diff --git a/kwin/client.cpp b/kwin/client.cpp index 7f8143bc59..a52bfcbd29 100644 --- a/kwin/client.cpp +++ b/kwin/client.cpp @@ -1,2501 +1,2499 @@ /******************************************************************** 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 #ifdef KWIN_BUILD_KAPPMENU #include "appmenu.h" #endif #include "atoms.h" #include "bridge.h" #include "client_machine.h" #include "composite.h" #include "cursor.h" #include "decorations.h" #include "deleted.h" #include "focuschain.h" #include "group.h" #include "paintredirector.h" #include "shadow.h" #ifdef KWIN_BUILD_TABBOX #include "tabbox.h" #endif #include "workspace.h" // KDE #include #include // Qt #include #include #include #include #ifdef KWIN_BUILD_SCRIPTING #include #include #endif #include // system #include #include // Put all externs before the namespace statement to allow the linker // to resolve them properly namespace KWin { bool Client::s_haveResizeEffect = false; // 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() : Toplevel() , m_client() , m_wrapper() , m_frame() , decoration(NULL) , bridge(new Bridge(this)) , m_activityUpdatesBlocked(false) , m_blockedActivityUpdatesRequireTransients(false) , m_moveResizeGrabWindow() , move_resize_has_keyboard_grab(false) , m_managed(false) , transient_for (NULL) , m_transientForId(XCB_WINDOW_NONE) , m_originalTransientForId(XCB_WINDOW_NONE) , shade_below(NULL) , skip_switcher(false) , blocks_compositing(false) , m_cursor(Qt::ArrowCursor) , autoRaiseTimer(NULL) , shadeHoverTimer(NULL) , delayedMoveResizeTimer(NULL) , m_colormap(XCB_COLORMAP_NONE) , in_group(NULL) , m_windowGroup(XCB_WINDOW_NONE) , tab_group(NULL) , in_layer(UnknownLayer) , ping_timer(NULL) , m_killHelperPID(0) , m_pingTimestamp(XCB_TIME_CURRENT_TIME) , m_userTime(XCB_TIME_CURRENT_TIME) // Not known yet , allowed_actions(0) , block_geometry_updates(0) , pending_geometry_update(PendingGeometryNone) , shade_geometry_change(false) , border_left(0) , border_right(0) , border_top(0) , border_bottom(0) , padding_left(0) , padding_right(0) , padding_top(0) , padding_bottom(0) , sm_stacking_order(-1) , paintRedirector(0) , m_firstInTabBox(false) , electricMaximizing(false) , activitiesDefined(false) , needsSessionInteract(false) , needsXWindowMove(false) #ifdef KWIN_BUILD_KAPPMENU , m_menuAvailable(false) #endif , m_decoInputExtent() , m_focusOutTimer(nullptr) { // TODO: Do all as initialization syncRequest.counter = syncRequest.alarm = XCB_NONE; syncRequest.timeout = syncRequest.failsafeTimeout = NULL; syncRequest.isPending = false; // Set the initial mapping state mapping_state = Withdrawn; quick_tile_mode = QuickTileNone; desk = 0; // No desktop yet mode = PositionCenter; buttonDown = false; moveResizeMode = false; info = NULL; shade_mode = ShadeNone; active = false; deleting = false; keep_above = false; keep_below = false; motif_may_move = true; motif_may_resize = true; motif_may_close = true; fullscreen_mode = FullScreenNone; skip_taskbar = false; original_skip_taskbar = false; minimized = false; hidden = false; modal = false; noborder = false; app_noborder = false; motif_noborder = false; urgency = false; ignore_focus_stealing = false; demands_attention = false; check_active_modal = false; Pdeletewindow = 0; Ptakefocus = 0; Ptakeactivity = 0; Pcontexthelp = 0; Pping = 0; input = false; skip_pager = false; max_mode = MaximizeRestore; //Client to workspace connections require that each //client constructed be connected to the workspace wrapper #ifdef KWIN_BUILD_TABBOX // TabBoxClient m_tabBoxClient = QSharedPointer(new TabBox::TabBoxClientImpl(this)); #endif 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(this, SIGNAL(geometryShapeChanged(KWin::Toplevel*,QRect)), SIGNAL(geometryChanged())); connect(this, SIGNAL(clientMaximizedStateChanged(KWin::Client*,KDecorationDefines::MaximizeMode)), SIGNAL(geometryChanged())); connect(this, SIGNAL(clientStepUserMovedResized(KWin::Client*,QRect)), SIGNAL(geometryChanged())); connect(this, SIGNAL(clientStartUserMovedResized(KWin::Client*)), SIGNAL(moveResizedChanged())); connect(this, SIGNAL(clientFinishUserMovedResized(KWin::Client*)), SIGNAL(moveResizedChanged())); connect(this, SIGNAL(clientStartUserMovedResized(KWin::Client*)), SLOT(removeCheckScreenConnection())); connect(this, SIGNAL(clientFinishUserMovedResized(KWin::Client*)), SLOT(setupCheckScreenConnection())); connect(clientMachine(), SIGNAL(localhostChanged()), SLOT(updateCaption())); connect(options, SIGNAL(condensedTitleChanged()), SLOT(updateCaption())); // 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(!moveResizeMode); assert(m_client == XCB_WINDOW_NONE); assert(m_wrapper == XCB_WINDOW_NONE); //assert( frameId() == None ); assert(decoration == NULL); assert(block_geometry_updates == 0); assert(!check_active_modal); delete bridge; } // 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; Deleted* del = NULL; if (!on_shutdown) { del = Deleted::create(this); } if (moveResizeMode) emit clientFinishUserMovedResized(this); emit windowClosed(this, del); finishCompositing(); RuleBook::self()->discardUsed(this, true); // Remove ForceTemporarily rules StackingUpdatesBlocker blocker(workspace()); if (moveResizeMode) leaveMoveResize(); finishWindowRules(); ++block_geometry_updates; 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); desk = 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; --block_geometry_updates; // 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; Deleted* del = Deleted::create(this); if (moveResizeMode) emit clientFinishUserMovedResized(this); emit windowClosed(this, del); finishCompositing(); RuleBook::self()->discardUsed(this, true); // Remove ForceTemporarily rules StackingUpdatesBlocker blocker(workspace()); if (moveResizeMode) leaveMoveResize(); finishWindowRules(); ++block_geometry_updates; 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; --block_geometry_updates; // Don't use GeometryUpdatesBlocker, it would now set the geometry disownDataPassedToDeleted(); del->unrefWindow(); checkNonExistentClients(); deleteClient(this); } void Client::updateInputWindow() { QRegion region; if (!noBorder()) { // This function is implemented as a slot to avoid breaking binary // compatibility QMetaObject::invokeMethod(decoration, "region", Qt::DirectConnection, Q_RETURN_ARG(QRegion, region), Q_ARG(KDecorationDefines::Region, KDecorationDefines::ExtendedBorderRegion)); } 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 && ((decoration == NULL && noBorder()) || (decoration != NULL && !noBorder()))) return; QRect oldgeom = geometry(); blockGeometryUpdates(true); if (force) destroyDecoration(); if (!noBorder()) { createDecoration(oldgeom); } else destroyDecoration(); if (check_workspace_pos) checkWorkspacePosition(oldgeom); updateInputWindow(); blockGeometryUpdates(false); if (!noBorder()) decoration->widget()->show(); updateFrameExtents(); } void Client::createDecoration(const QRect& oldgeom) { setMask(QRegion()); // Reset shape mask if (decorationPlugin()->isDisabled()) { decoration = NULL; return; } else { decoration = decorationPlugin()->createDecoration(bridge); } connect(this, SIGNAL(shadeChanged()), decoration, SLOT(shadeChange())); connect(this, SIGNAL(desktopChanged()), decoration, SLOT(desktopChange())); connect(this, SIGNAL(captionChanged()), decoration, SLOT(captionChange())); connect(this, SIGNAL(activeChanged()), decoration, SLOT(activeChange())); connect(this, SIGNAL(clientMaximizedStateChanged(KWin::Client*,KDecorationDefines::MaximizeMode)), decoration, SLOT(maximizeChange())); connect(this, SIGNAL(keepAboveChanged(bool)), decoration, SIGNAL(keepAboveChanged(bool))); connect(this, SIGNAL(keepBelowChanged(bool)), decoration, SIGNAL(keepBelowChanged(bool))); #ifdef KWIN_BUILD_KAPPMENU connect(this, SIGNAL(showRequest()), decoration, SIGNAL(showRequest())); connect(this, SIGNAL(appMenuAvailable()), decoration, SIGNAL(appMenuAvailable())); connect(this, SIGNAL(appMenuUnavailable()), decoration, SIGNAL(appMenuUnavailable())); connect(this, SIGNAL(menuHidden()), decoration, SIGNAL(menuHidden())); #endif // TODO: Check decoration's minimum size? decoration->init(); decoration->widget()->installEventFilter(this); xcb_reparent_window(connection(), decoration->widget()->winId(), frameId(), 0, 0); decoration->widget()->lower(); decoration->borders(border_left, border_right, border_top, border_bottom); padding_left = padding_right = padding_top = padding_bottom = 0; - if (KDecorationUnstable *deco2 = dynamic_cast(decoration)) - deco2->padding(padding_left, padding_right, padding_top, padding_bottom); + decoration->padding(padding_left, padding_right, padding_top, padding_bottom); Xcb::moveWindow(decoration->widget()->winId(), -padding_left, -padding_top); move(calculateGravitation(false)); plainResize(sizeForClientSize(clientSize()), ForceGeometrySet); if (Compositor::compositing()) { paintRedirector = PaintRedirector::create(this, decoration->widget()); discardWindowPixmap(); } emit geometryShapeChanged(this, oldgeom); } void Client::destroyDecoration() { QRect oldgeom = geometry(); if (decoration != NULL) { delete decoration; decoration = NULL; paintRedirector = NULL; QPoint grav = calculateGravitation(true); border_left = border_right = border_top = border_bottom = 0; setMask(QRegion()); // Reset shape mask plainResize(sizeForClientSize(clientSize()), ForceGeometrySet); move(grav); if (compositing()) discardWindowPixmap(); if (!deleting) { emit geometryShapeChanged(this, oldgeom); } } m_decoInputExtent.reset(); } bool Client::checkBorderSizes(bool also_resize) { if (decoration == NULL) return false; int new_left = 0, new_right = 0, new_top = 0, new_bottom = 0; - if (KDecorationUnstable *deco2 = dynamic_cast(decoration)) - deco2->padding(new_left, new_right, new_top, new_bottom); + decoration->padding(new_left, new_right, new_top, new_bottom); if (padding_left != new_left || padding_top != new_top) Xcb::moveWindow(decoration->widget()->winId(), -new_left, -new_top); padding_left = new_left; padding_right = new_right; padding_top = new_top; padding_bottom = new_bottom; decoration->borders(new_left, new_right, new_top, new_bottom); if (new_left == border_left && new_right == border_right && new_top == border_top && new_bottom == border_bottom) return false; if (!also_resize) { border_left = new_left; border_right = new_right; border_top = new_top; border_bottom = new_bottom; return true; } GeometryUpdatesBlocker blocker(this); move(calculateGravitation(true)); border_left = new_left; border_right = new_right; border_top = new_top; border_bottom = new_bottom; move(calculateGravitation(false)); QRect oldgeom = geometry(); plainResize(sizeForClientSize(clientSize()), ForceGeometrySet); checkWorkspacePosition(oldgeom); return true; } void Client::triggerDecorationRepaint() { if (decoration != NULL) decoration->widget()->update(); } void Client::layoutDecorationRects(QRect &left, QRect &top, QRect &right, QRect &bottom, Client::CoordinateMode mode) const { QRect r = decoration->widget()->rect(); if (mode == WindowRelative) r.translate(-padding_left, -padding_top); NETStrut strut = info->frameOverlap(); // Ignore the overlap strut when compositing is disabled if (!compositing() || !decorationPlugin()->supportsFrameOverlap()) 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(), padding_top + border_top + strut.top); bottom = QRect(r.x(), r.y() + r.height() - padding_bottom - border_bottom - strut.bottom, r.width(), padding_bottom + border_bottom + strut.bottom); left = QRect(r.x(), r.y() + top.height(), padding_left + border_left + strut.left, r.height() - top.height() - bottom.height()); right = QRect(r.x() + r.width() - padding_right - border_right - strut.right, r.y() + top.height(), padding_right + border_right + strut.right, r.height() - top.height() - bottom.height()); } QRegion Client::decorationPendingRegion() const { if (!paintRedirector) return QRegion(); return paintRedirector->scheduledRepaintRegion().translated(x() - padding_left, y() - padding_top); } 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() || !decorationPlugin()->supportsFrameOverlap()) 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 : 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(SUPPORTED_MANAGED_WINDOW_TYPES_MASK | NET::OverrideMask) == NET::Override) { noborder = true; app_noborder = true; } } void Client::updateFrameExtents() { NETStrut strut; strut.left = border_left; strut.right = border_right; strut.top = border_top; strut.bottom = border_bottom; info->setFrameExtents(strut); } /** * 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(const QSize& s) { if (decoration == NULL) return; QSize newSize = s + QSize(padding_left + padding_right, padding_top + padding_bottom); QSize oldSize = decoration->widget()->size(); decoration->resize(newSize); if (oldSize == newSize) { QResizeEvent e(newSize, oldSize); QApplication::sendEvent(decoration->widget(), &e); } else if (paintRedirector) { // oldSize != newSize paintRedirector->resizePixmaps(); } else { triggerDecorationRepaint(); } updateInputWindow(); } bool Client::noBorder() const { return decorationPlugin()->isDisabled() || 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); } 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 = rules()->checkNoBorder(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::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::setMask(const QRegion& reg, int mode) { QRegion r = reg.translated(-padding_left, -padding_right) & QRect(0, 0, width(), height()); if (_mask == r) return; _mask = r; xcb_connection_t *c = connection(); xcb_window_t shape_window = frameId(); if (shape()) { // The same way of applying a shape without strange intermediate states like above if (!shape_helper_window.isValid()) shape_helper_window.create(QRect(0, 0, 1, 1)); shape_window = shape_helper_window; } if (_mask.isEmpty()) { xcb_shape_mask(c, XCB_SHAPE_SO_SET, XCB_SHAPE_SK_BOUNDING, shape_window, 0, 0, XCB_PIXMAP_NONE); } else { const QVector< QRect > rects = _mask.rects(); QVector< xcb_rectangle_t > xrects(rects.count()); for (int i = 0; i < rects.count(); ++i) { const QRect &rect = rects.at(i); xcb_rectangle_t xrect; xrect.x = rect.x(); xrect.y = rect.y(); xrect.width = rect.width(); xrect.height = rect.height(); xrects[i] = xrect; } xcb_shape_rectangles(c, XCB_SHAPE_SO_SET, XCB_SHAPE_SK_BOUNDING, mode, shape_window, 0, 0, xrects.count(), xrects.constData()); } if (shape()) { // The rest of the applying using a temporary window xcb_rectangle_t rec = { 0, 0, static_cast(clientSize().width()), static_cast(clientSize().height()) }; xcb_shape_rectangles(c, XCB_SHAPE_SO_SUBTRACT, XCB_SHAPE_SK_BOUNDING, XCB_CLIP_ORDERING_UNSORTED, shape_helper_window, clientPos().x(), clientPos().y(), 1, &rec); xcb_shape_combine(c, XCB_SHAPE_SO_UNION, XCB_SHAPE_SK_BOUNDING, XCB_SHAPE_SK_BOUNDING, shape_helper_window, clientPos().x(), clientPos().y(), window()); xcb_shape_combine(c, XCB_SHAPE_SO_SET, XCB_SHAPE_SK_BOUNDING, XCB_SHAPE_SK_BOUNDING, frameId(), 0, 0, shape_helper_window); } emit geometryShapeChanged(this, geometry()); updateShape(); } QRegion Client::mask() const { if (_mask.isEmpty()) return QRegion(0, 0, width(), height()); return _mask; } 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; ClientList mainclients = mainClients(); for (ClientList::ConstIterator 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::setMinimized(bool set) { set ? minimize() : unminimize(); } /** * Minimizes this client plus its transients */ void Client::minimize(bool avoid_animation) { if (!isMinimizable() || isMinimized()) return; if (isShade()) // NETWM restriction - KWindowInfo::isMinimized() == Hidden && !Shaded info->setState(0, NET::Shaded); minimized = true; updateVisibility(); updateAllowedActions(); workspace()->updateMinimizedOfTransients(this); updateWindowRules(Rules::Minimize); FocusChain::self()->update(this, FocusChain::MakeFirstMinimized); // TODO: merge signal with s_minimized emit clientMinimized(this, !avoid_animation); // Update states of all other windows in this group if (tabGroup()) tabGroup()->updateStates(this, TabGroup::Minimized); emit minimizedChanged(); } void Client::unminimize(bool avoid_animation) { if (!isMinimized()) return; if (rules()->checkMinimize(false)) { return; } if (isShade()) // NETWM restriction - KWindowInfo::isMinimized() == Hidden && !Shaded info->setState(NET::Shaded, NET::Shaded); minimized = false; updateVisibility(); updateAllowedActions(); workspace()->updateMinimizedOfTransients(this); updateWindowRules(Rules::Minimize); emit clientUnminimized(this, !avoid_animation); // Update states of all other windows in this group if (tabGroup()) tabGroup()->updateStates(this, TabGroup::Minimized); emit minimizedChanged(); } 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 (Client * mainwin, mainClients()) { 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(bool set) { set ? setShade(ShadeNormal) : setShade(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 (decoration) decoration->borders(border_left, border_right, border_top, border_bottom); // 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(decoration != NULL); // 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(border_top + border_bottom); 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); if (isActive()) workspace()->activateNextClient(this); } else if (isActive()) { workspace()->focusToNull(); } } else { shade_geometry_change = true; QSize s(sizeForClientSize(clientSize())); shade_geometry_change = false; plainResize(s); if ((shade_mode == ShadeHover || shade_mode == ShadeActivated) && rules()->checkAcceptFocus(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 : 0, NET::Shaded); info->setState(isShown(false) ? 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, false); // Also hide from taskbar if (compositing() && options->hiddenPreviews() == HiddenPreviewsAlways) internalKeep(); else internalHide(); return; } if (isCurrentTab()) setSkipTaskbar(original_skip_taskbar, false); // Reset from 'hidden' if (minimized) { 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; } if (isManaged()) resetShowingDesktop(true); internalShow(); } void Client::resetShowingDesktop(bool keep_hidden) { if (isDock() || !workspace()->showingDesktop()) return; bool belongs_to_desktop = false; for (ClientList::ConstIterator it = group()->members().constBegin(), end = group()->members().constEnd(); it != end; ++it) if ((belongs_to_desktop = (*it)->isDesktop())) break; if (!belongs_to_desktop) workspace()->resetShowingDesktop(keep_hidden); } /** * 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(); } } 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); 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(); if (decoration != NULL) decoration->widget()->show(); // Not really necessary, but let it know the state m_frame.map(); if (!isShade()) { m_wrapper.map(); m_client.map(); m_decoInputExtent.map(); exportMappingState(NormalState); } else exportMappingState(IconicState); } /** * 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); if (decoration != NULL) decoration->widget()->hide(); // Not really necessary, but let it know the state 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, long data1, long data2, long data3) { 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] = xTime(); 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(motif_may_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 (Pdeletewindow) { 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() { qDebug() << "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 (!Pping) 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, SIGNAL(timeout()), SLOT(pingTimeout())); 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::pingTimeout() { qDebug() << "Ping timeout:" << caption(); ping_timer->deleteLater(); ping_timer = NULL; killProcess(true, m_pingTimestamp); } 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; qDebug() << "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(QStandardPaths::findExecutable(QStringLiteral("kwin_killer_helper")), 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::setSkipTaskbar(bool b, bool from_outside) { int was_wants_tab_focus = wantsTabFocus(); if (from_outside) { b = rules()->checkSkipTaskbar(b); original_skip_taskbar = b; } if (b == skipTaskbar()) return; skip_taskbar = b; info->setState(b ? NET::SkipTaskbar : 0, NET::SkipTaskbar); updateWindowRules(Rules::SkipTaskbar); if (was_wants_tab_focus != wantsTabFocus()) FocusChain::self()->update(this, isActive() ? FocusChain::MakeFirst : FocusChain::Update); emit skipTaskbarChanged(); } void Client::setSkipPager(bool b) { b = rules()->checkSkipPager(b); if (b == skipPager()) return; skip_pager = b; info->setState(b ? NET::SkipPager : 0, NET::SkipPager); updateWindowRules(Rules::SkipPager); emit skipPagerChanged(); } void Client::setSkipSwitcher(bool set) { set = rules()->checkSkipSwitcher(set); if (set == skipSwitcher()) return; skip_switcher = set; updateWindowRules(Rules::SkipSwitcher); emit skipSwitcherChanged(); } void Client::setModal(bool m) { // Qt-3.2 can have even modal normal windows :( if (modal == m) return; modal = m; emit modalChanged(); // Changing modality for a mapped window is weird (?) // _NET_WM_STATE_MODAL should possibly rather be _NET_WM_WINDOW_TYPE_MODAL_DIALOG } void Client::setDesktop(int desktop) { const int numberOfDesktops = VirtualDesktopManager::self()->count(); if (desktop != NET::OnAllDesktops) // Do range check desktop = qMax(1, qMin(numberOfDesktops, desktop)); desktop = qMin(numberOfDesktops, rules()->checkDesktop(desktop)); if (desk == desktop) return; int was_desk = desk; desk = desktop; info->setDesktop(desktop); if ((was_desk == NET::OnAllDesktops) != (desktop == NET::OnAllDesktops)) { // onAllDesktops changed workspace()->updateOnAllDesktopsOfTransients(this); } ClientList transients_stacking_order = workspace()->ensureStackingOrder(transients()); for (ClientList::ConstIterator it = transients_stacking_order.constBegin(); it != transients_stacking_order.constEnd(); ++it) (*it)->setDesktop(desktop); if (isModal()) // if a modal dialog is moved, move the mainwindow with it as otherwise // the (just moved) modal dialog will confusingly return to the mainwindow with // the next desktop change { foreach (Client * c2, mainClients()) c2->setDesktop(desktop); } FocusChain::self()->update(this, FocusChain::MakeFirst); updateVisibility(); updateWindowRules(Rules::Desktop); // Update states of all other windows in this group if (tabGroup()) tabGroup()->updateStates(this, TabGroup::Desktop); emit desktopChanged(); } /** * 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 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 QString joinedActivitiesList = newActivitiesList.join(QStringLiteral(",")); joinedActivitiesList = rules()->checkActivity(joinedActivitiesList, false); newActivitiesList = joinedActivitiesList.split(QStringLiteral(","), QString::SkipEmptyParts); QStringList allActivities = Activities::self()->all(); if ( newActivitiesList.isEmpty() || (newActivitiesList.count() > 1 && newActivitiesList.count() == allActivities.count()) || (newActivitiesList.count() == 1 && newActivitiesList.at(0) == Activities::nullUuid())) { 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 desk; } /** * 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; } void Client::setOnAllDesktops(bool b) { if ((b && isOnAllDesktops()) || (!b && !isOnAllDesktops())) return; if (b) setDesktop(NET::OnAllDesktops); else setDesktop(VirtualDesktopManager::self()->current()); // Update states of all other windows in this group if (tabGroup()) tabGroup()->updateStates(this, TabGroup::Desktop); } /** * 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); } #endif } /** * Performs activation and/or raising of the window */ void Client::takeActivity(int flags, bool handled) { if (!handled || !Ptakeactivity) { if (flags & ActivityFocus) takeFocus(); if (flags & ActivityRaise) workspace()->raiseClient(this); return; } #ifndef NDEBUG static Time previous_activity_timestamp; static Client* previous_client; //if ( previous_activity_timestamp == xTime() && previous_client != this ) // { // kDebug( 1212 ) << "Repeated use of the same X timestamp for activity"; // kDebug( 1212 ) << kBacktrace(); // } previous_activity_timestamp = xTime(); previous_client = this; #endif workspace()->sendTakeActivity(this, xTime(), flags); } /** * Performs the actual focusing of the window using XSetInputFocus and WM_TAKE_FOCUS */ void Client::takeFocus() { #ifndef NDEBUG static Time previous_focus_timestamp; static Client* previous_client; //if ( previous_focus_timestamp == xTime() && previous_client != this ) // { // kDebug( 1212 ) << "Repeated use of the same X timestamp for focus"; // kDebug( 1212 ) << kBacktrace(); // } previous_focus_timestamp = xTime(); previous_client = this; #endif if (rules()->checkAcceptFocus(input)) m_client.focus(); else demandAttention(false); // window cannot take input, at least withdraw urgency if (Ptakefocus) sendClientMessage(window(), atoms->wm_protocols, atoms->wm_take_focus); workspace()->setShouldGetFocus(this); } /** * 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 Pcontexthelp; } /** * Invokes context help on the window. Only works if the window * actually provides context help. * * \sa providesContextHelp() */ void Client::showContextHelp() { if (Pcontexthelp) { 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(), XA_WM_NAME); } KWIN_COMPARE_PREDICATE(FetchNameInternalPredicate, Client, const Client*, (!cl->isSpecialWindow() || cl->isToolbar()) && cl != value && cl->caption() == value->caption()); // The list is taken from http://www.unicode.org/reports/tr9/ (#154840) QChar LRM(0x200E); QChar RLM(0x200F); QChar LRE(0x202A); QChar RLE(0x202B); QChar LRO(0x202D); QChar RLO(0x202E); 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; #ifdef KWIN_BUILD_SCRIPTING 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) + QStringLiteral("/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(); } #endif 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 = QStringLiteral(" <@") + QString::fromUtf8(clientMachine()->hostName()) + QStringLiteral(">") + LRM; } QString shortcut_suffix = !shortcut().isEmpty() ? (QStringLiteral(" {") + shortcut().toString() + QStringLiteral("}")) : QString(); cap_suffix = machine_suffix + shortcut_suffix; if ((!isSpecialWindow() || isToolbar()) && workspace()->findClient(FetchNameInternalPredicate(this))) { int i = 2; do { cap_suffix = machine_suffix + QStringLiteral(" <") + QString::number(i) + QStringLiteral(">") + LRM; i++; } while (workspace()->findClient(FetchNameInternalPredicate(this))); 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::fetchIconicName() { QString s; if (info->iconName() && info->iconName()[0] != '\0') s = QString::fromUtf8(info->iconName()); else s = KWindowSystem::readNameProperty(window(), XA_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() != Client::MaximizeRestore) { changedSize = true; maximize(Client::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::dontMoveResize() { buttonDown = false; stopDelayedMoveResize(); if (moveResizeMode) finishMoveResize(false); } 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, false); // 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::getWMHints() { XWMHints* hints = XGetWMHints(display(), window()); input = true; m_windowGroup = XCB_WINDOW_NONE; urgency = false; if (hints) { if (hints->flags & InputHint) input = hints->input; if (hints->flags & WindowGroupHint) m_windowGroup = hints->window_group; urgency = !!(hints->flags & UrgencyHint); // Need boolean, it's a uint bitfield XFree((char*)hints); } checkGroup(); updateUrgency(); updateAllowedActions(); // Group affects isMinimizable() } void Client::getMotifHints() { bool mgot_noborder, mnoborder, mresize, mmove, mminimize, mmaximize, mclose; Motif::readFlags(m_client, mgot_noborder, mnoborder, mresize, mmove, mminimize, mmaximize, mclose); if (mgot_noborder && motif_noborder != mnoborder) { motif_noborder = mnoborder; // If we just got a hint telling us to hide decorations, we do so. if (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); } if (!hasNETSupport()) { // NETWM apps should set type and size constraints motif_may_resize = mresize; // This should be set using minsize==maxsize, but oh well motif_may_move = mmove; } else motif_may_resize = motif_may_move = true; // 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 = motif_may_close != mclose; motif_may_close = mclose; // Motif apps like to crash when they set this hint and WM closes them anyway if (isManaged()) updateDecoration(true); // Check if noborder state has changed if (decoration && closabilityChanged) decoration->reset(KDecoration::SettingButtons); } void Client::readIcons(xcb_window_t win, QPixmap* icon, QPixmap* miniicon, QPixmap* bigicon, QPixmap* hugeicon) { // Get the icons, allow scaling if (icon != NULL) *icon = KWindowSystem::icon(win, 32, 32, true, KWindowSystem::NETWM | KWindowSystem::WMHints); if (miniicon != NULL) { if (icon == NULL || !icon->isNull()) *miniicon = KWindowSystem::icon(win, 16, 16, true, KWindowSystem::NETWM | KWindowSystem::WMHints); else *miniicon = QPixmap(); } if (bigicon != NULL) { if (icon == NULL || !icon->isNull()) *bigicon = KWindowSystem::icon(win, 64, 64, false, KWindowSystem::NETWM | KWindowSystem::WMHints); else *bigicon = QPixmap(); } if (hugeicon != NULL) { if (icon == NULL || !icon->isNull()) *hugeicon = KWindowSystem::icon(win, 128, 128, false, KWindowSystem::NETWM | KWindowSystem::WMHints); else *hugeicon = QPixmap(); } } void Client::getIcons() { // First read icons from the window itself readIcons(window(), &icon_pix, &miniicon_pix, &bigicon_pix, &hugeicon_pix); if (icon_pix.isNull()) { // Then try window group icon_pix = group()->icon(); miniicon_pix = group()->miniIcon(); bigicon_pix = group()->bigIcon(); hugeicon_pix = group()->hugeIcon(); } if (icon_pix.isNull() && isTransient()) { // Then mainclients ClientList mainclients = mainClients(); for (ClientList::ConstIterator it = mainclients.constBegin(); it != mainclients.constEnd() && icon_pix.isNull(); ++it) { icon_pix = (*it)->icon(); miniicon_pix = (*it)->miniIcon(); bigicon_pix = (*it)->bigIcon(); hugeicon_pix = (*it)->hugeIcon(); } } if (icon_pix.isNull()) { // And if nothing else, load icon from classhint or xapp icon icon_pix = KWindowSystem::icon(window(), 32, 32, true, KWindowSystem::ClassHint | KWindowSystem::XApp); miniicon_pix = KWindowSystem::icon(window(), 16, 16, true, KWindowSystem::ClassHint | KWindowSystem::XApp); bigicon_pix = KWindowSystem::icon(window(), 64, 64, false, KWindowSystem::ClassHint | KWindowSystem::XApp); hugeicon_pix = KWindowSystem::icon(window(), 128, 128, false, KWindowSystem::ClassHint | KWindowSystem::XApp); } emit iconChanged(); } QPixmap Client::icon(const QSize& size) const { const int iconSize = qMin(size.width(), size.height()); if (iconSize <= 16) return miniIcon(); else if (iconSize <= 32) return icon(); if (iconSize <= 64) return bigIcon(); else return hugeIcon(); } void Client::getWindowProtocols() { Atom* p; int i, n; Pdeletewindow = 0; Ptakefocus = 0; Ptakeactivity = 0; Pcontexthelp = 0; Pping = 0; if (XGetWMProtocols(display(), window(), &p, &n)) { for (i = 0; i < n; ++i) { if (p[i] == atoms->wm_delete_window) Pdeletewindow = 1; else if (p[i] == atoms->wm_take_focus) Ptakefocus = 1; else if (p[i] == atoms->net_wm_take_activity) Ptakeactivity = 1; else if (p[i] == atoms->net_wm_context_help) Pcontexthelp = 1; else if (p[i] == atoms->net_wm_ping) Pping = 1; } if (n > 0) XFree(p); } } void Client::getSyncCounter() { if (!Xcb::Extensions::self()->isSyncAvailable()) return; Atom retType; unsigned long nItemRet; unsigned long byteRet; int formatRet; unsigned char* propRet; int ret = XGetWindowProperty(display(), window(), atoms->net_wm_sync_request_counter, 0, 1, false, XA_CARDINAL, &retType, &formatRet, &nItemRet, &byteRet, &propRet); if (ret == Success && formatRet == 32) { syncRequest.counter = *(long*)(propRet); syncRequest.value.hi = 0; syncRequest.value.lo = 0; xcb_sync_int64_t zero; zero.hi = 0; zero.lo = 0; auto *c = connection(); xcb_sync_set_counter(c, syncRequest.counter, zero); if (syncRequest.alarm == XCB_NONE) { const uint32_t mask = XCB_SYNC_CA_COUNTER | XCB_SYNC_CA_VALUE_TYPE | XCB_SYNC_CA_VALUE | XCB_SYNC_CA_TEST_TYPE | XCB_SYNC_CA_DELTA ; const uint32_t values[] = { syncRequest.counter, XCB_SYNC_VALUETYPE_RELATIVE, 0, 1, XCB_SYNC_TESTTYPE_POSITIVE_TRANSITION, 0, 1 }; syncRequest.alarm = xcb_generate_id(c); xcb_sync_create_alarm(c, syncRequest.alarm, mask, values); } } if (ret == Success) XFree(propRet); } /** * 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, SIGNAL(timeout()), SLOT(removeSyncSupport())); 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++; } // Send the message to client XEvent ev; ev.xclient.type = ClientMessage; ev.xclient.window = window(); ev.xclient.format = 32; ev.xclient.message_type = atoms->wm_protocols; ev.xclient.data.l[0] = atoms->net_wm_sync_request; ev.xclient.data.l[1] = xTime(); ev.xclient.data.l[2] = syncRequest.value.lo; ev.xclient.data.l[3] = syncRequest.value.hi; ev.xclient.data.l[4] = 0; syncRequest.isPending = true; XSendEvent(display(), window(), False, NoEventMask, &ev); Xcb::sync(); } void Client::removeSyncSupport() { if (!ready_for_painting) { setReadyForPainting(); return; } syncRequest.isPending = false; syncRequest.counter = syncRequest.alarm = XCB_NONE; delete syncRequest.timeout; delete syncRequest.failsafeTimeout; syncRequest.timeout = syncRequest.failsafeTimeout = NULL; } bool Client::wantsTabFocus() const { return (isNormalWindow() || isDialog()) && wantsInput(); } bool Client::wantsInput() const { return rules()->checkAcceptFocus(input || Ptakefocus); } bool Client::isSpecialWindow() const { // TODO return isDesktop() || isDock() || isSplash() || isToolbar(); } /** * Sets an appropriate cursor shape for the logical mouse position \a m */ void Client::updateCursor() { Position m = mode; if (!isResizable() || isShade()) m = PositionCenter; Qt::CursorShape c = Qt::ArrowCursor; switch(m) { case PositionTopLeft: case PositionBottomRight: c = Qt::SizeFDiagCursor; break; case PositionBottomLeft: case PositionTopRight: c = Qt::SizeBDiagCursor; break; case PositionTop: case PositionBottom: c = Qt::SizeVerCursor; break; case PositionLeft: case PositionRight: c = Qt::SizeHorCursor; break; default: if (moveResizeMode) c = Qt::SizeAllCursor; else c = Qt::ArrowCursor; break; } if (c == m_cursor) return; m_cursor = c; if (decoration != NULL) decoration->widget()->setCursor(m_cursor); xcb_cursor_t nativeCursor = Cursor::x11Cursor(m_cursor); m_frame.defineCursor(nativeCursor); if (m_decoInputExtent.isValid()) m_decoInputExtent.defineCursor(nativeCursor); if (moveResizeMode) { // 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); } } void Client::updateCompositeBlocking(bool readProperty) { if (readProperty) { const unsigned long properties[2] = {0, NET::WM2BlockCompositing}; NETWinInfo2 i(display(), window(), rootWindow(), properties, 2); setBlockingCompositing(i.isBlockingCompositing()); } else setBlockingCompositing(blocks_compositing); } 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); } } Client::Position Client::mousePosition(const QPoint& p) const { if (decoration != NULL) return decoration->mousePosition(p); return PositionCenter; } void Client::updateAllowedActions(bool force) { if (!isManaged() && !force) return; unsigned long old_allowed_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 unsigned long relevant = ~(NET::ActionMove|NET::ActionResize); if (decoration && (allowed_actions & relevant) != (old_allowed_actions & relevant)) decoration->reset(KDecoration::SettingButtons); } void Client::autoRaise() { workspace()->raiseClient(this); cancelAutoRaise(); } void Client::cancelAutoRaise() { delete autoRaiseTimer; autoRaiseTimer = 0; } void Client::debug(QDebug& stream) const { print(stream); } QPixmap* kwin_get_menu_pix_hack() { static QPixmap p; if (p.isNull()) p = SmallIcon(QStringLiteral("bx2")); return &p; } void Client::checkActivities() { #ifdef KWIN_BUILD_ACTIVITIES QStringList newActivitiesList; QByteArray prop = getStringProperty(window(), atoms->activities); activitiesDefined = !prop.isEmpty(); if (QString::fromUtf8(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 = QString::fromUtf8(prop).split(QStringLiteral(",")); if (newActivitiesList == activityList) return; //expected change, it's ok. //otherwise, somebody else changed it. we need to validate before reacting QStringList allActivities = Activities::self()->all(); if (allActivities.isEmpty()) { kDebug() << "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))) { kDebug() << "invalid:" << newActivitiesList.at(i); newActivitiesList.removeAt(i--); } } setOnActivities(newActivitiesList); #endif } void Client::setSessionInteract(bool needed) { needsSessionInteract = needed; } QRect Client::decorationRect() const { if (decoration && decoration->widget()) { return decoration->widget()->rect().translated(-padding_left, -padding_top); } else { return QRect(0, 0, width(), height()); } } KDecorationDefines::Position Client::titlebarPosition() { Position titlePos = PositionCenter; // PositionTop is returned by the default implementation // this will hint errors in the metaobject usage ;-) if (decoration) QMetaObject::invokeMethod(decoration, "titlebarPosition", Qt::DirectConnection, Q_RETURN_ARG(KDecorationDefines::Position, titlePos)); return titlePos; } void Client::updateFirstInTabBox() { // TODO: move into KWindowInfo xcb_connection_t *c = connection(); const auto cookie = xcb_get_property_unchecked(c, false, m_client, atoms->kde_first_in_window_list, atoms->kde_first_in_window_list, 0, 1); ScopedCPointer prop(xcb_get_property_reply(c, cookie, nullptr)); if (!prop.isNull() && prop->format == 32 && prop->value_len == 1) { setFirstInTabBox(true); } else { setFirstInTabBox(false); } } bool Client::isClient() const { return true; } #ifdef KWIN_BUILD_KAPPMENU void Client::setAppMenuAvailable() { m_menuAvailable = true; emit appMenuAvailable(); } void Client::setAppMenuUnavailable() { m_menuAvailable = false; emit appMenuUnavailable(); } void Client::showApplicationMenu(const QPoint &p) { ApplicationMenu::self()->showApplicationMenu(p, window()); } #endif 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(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; } bool Client::decorationHasAlpha() const { if (!decoration || !decorationPlugin()->hasAlpha()) { // either no decoration or decoration has alpha disabled return false; } if (decorationPlugin()->supportsAnnounceAlpha()) { return decoration->isAlphaEnabled(); } else { // decoration has alpha enabled and does not support alpha announcement return true; } } void Client::cancelFocusOutTimer() { if (m_focusOutTimer) { m_focusOutTimer->stop(); } } xcb_window_t Client::frameId() const { return m_frame; } } // namespace #include "client.moc" diff --git a/kwin/clients/aurorae/src/aurorae.cpp b/kwin/clients/aurorae/src/aurorae.cpp index 9a0c707d38..b7dc633564 100644 --- a/kwin/clients/aurorae/src/aurorae.cpp +++ b/kwin/clients/aurorae/src/aurorae.cpp @@ -1,662 +1,662 @@ /******************************************************************** Copyright (C) 2009, 2010, 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 "aurorae.h" #include "auroraetheme.h" #include "config-kwin.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Aurorae { AuroraeFactory::AuroraeFactory() : QObject() , KDecorationFactoryUnstable() , m_theme(new AuroraeTheme(this)) , m_engine(new QDeclarativeEngine(this)) , m_component(new QDeclarativeComponent(m_engine, this)) , m_engineType(AuroraeEngine) { init(); } void AuroraeFactory::init() { qRegisterMetaType("Qt::MouseButtons"); KConfig conf(QStringLiteral("auroraerc")); KConfigGroup group(&conf, "Engine"); if (!group.hasKey("EngineType") && !group.hasKey("ThemeName")) { // neither engine type and theme name are configured, use the only available theme initQML(group); } else if (group.hasKey("EngineType")) { const QString engineType = group.readEntry("EngineType", "aurorae").toLower(); if (engineType == QStringLiteral("qml")) { initQML(group); } else { // fallback to classic Aurorae Themes initAurorae(conf, group); } } else { // fallback to classic Aurorae Themes initAurorae(conf, group); } } void AuroraeFactory::initAurorae(KConfig &conf, KConfigGroup &group) { m_engineType = AuroraeEngine; const QString themeName = group.readEntry("ThemeName"); if (themeName.isEmpty()) { // no theme configured, fall back to Plastik QML theme initQML(group); return; } KConfig config(QStringLiteral("aurorae/themes/") + themeName + QStringLiteral("/") + themeName + QStringLiteral("rc"), KConfig::FullConfig, QStandardPaths::DataLocation); KConfigGroup themeGroup(&conf, themeName); m_theme->loadTheme(themeName, config); m_theme->setBorderSize((KDecorationDefines::BorderSize)themeGroup.readEntry("BorderSize", KDecorationDefines::BorderNormal)); m_theme->setButtonSize((KDecorationDefines::BorderSize)themeGroup.readEntry("ButtonSize", KDecorationDefines::BorderNormal)); m_theme->setTabDragMimeType(tabDragMimeType()); // setup the QML engine /* use logic from KDeclarative::setupBindings(): "addImportPath adds the path at the beginning, so to honour user's paths we need to traverse the list in reverse order" */ QStringListIterator paths(QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("module/imports"), QStandardPaths::LocateDirectory)); paths.toBack(); while (paths.hasPrevious()) { m_engine->addImportPath(paths.previous()); } m_component->loadUrl(QUrl(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kwin/aurorae/aurorae.qml")))); m_engine->rootContext()->setContextProperty(QStringLiteral("auroraeTheme"), m_theme); m_themeName = themeName; } void AuroraeFactory::initQML(const KConfigGroup &group) { // try finding the QML package const QString themeName = group.readEntry("ThemeName", "kwin4_decoration_qml_plastik"); qDebug() << "Trying to load QML Decoration " << themeName; const QString internalname = themeName.toLower(); QString constraint = QStringLiteral("[X-KDE-PluginInfo-Name] == '%1'").arg(internalname); KService::List offers = KServiceTypeTrader::self()->query(QStringLiteral("KWin/Decoration"), constraint); if (offers.isEmpty()) { qCritical() << "Couldn't find QML Decoration " << themeName << endl; // TODO: what to do in error case? return; } KService::Ptr service = offers.first(); KPluginInfo plugininfo(service); const QString pluginName = service->property(QStringLiteral("X-KDE-PluginInfo-Name")).toString(); const QString scriptName = service->property(QStringLiteral("X-Plasma-MainScript")).toString(); const QString file = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral(KWIN_NAME) + QStringLiteral("/decorations/") + pluginName + QStringLiteral("/contents/") + scriptName); if (file.isNull()) { qDebug() << "Could not find script file for " << pluginName; // TODO: what to do in error case? return; } m_engineType = QMLEngine; // setup the QML engine /* use logic from KDeclarative::setupBindings(): "addImportPath adds the path at the beginning, so to honour user's paths we need to traverse the list in reverse order" */ QStringListIterator paths(QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("module/imports"), QStandardPaths::LocateDirectory)); paths.toBack(); while (paths.hasPrevious()) { m_engine->addImportPath(paths.previous()); } m_component->loadUrl(QUrl::fromLocalFile(file)); m_themeName = themeName; } AuroraeFactory::~AuroraeFactory() { s_instance = NULL; } AuroraeFactory *AuroraeFactory::instance() { if (!s_instance) { s_instance = new AuroraeFactory; } return s_instance; } bool AuroraeFactory::reset(unsigned long changed) { if (changed & SettingButtons) { emit buttonsChanged(); } if (changed & SettingFont){ emit titleFontChanged(); } if (changed & SettingCompositing) { return false; } const KConfig conf(QStringLiteral("auroraerc")); const KConfigGroup group(&conf, "Engine"); const QString themeName = group.readEntry("ThemeName", "example-deco"); const KConfig config(QStringLiteral("aurorae/themes/") + themeName + QStringLiteral("/") + themeName + QStringLiteral("rc"), KConfig::FullConfig, QStandardPaths::DataLocation); const KConfigGroup themeGroup(&conf, themeName); if (themeName != m_themeName) { m_engine->clearComponentCache(); init(); // recreate all decorations return true; } if (m_engineType == AuroraeEngine) { m_theme->setBorderSize((KDecorationDefines::BorderSize)themeGroup.readEntry("BorderSize", KDecorationDefines::BorderNormal)); m_theme->setButtonSize((KDecorationDefines::BorderSize)themeGroup.readEntry("ButtonSize", KDecorationDefines::BorderNormal)); } emit configChanged(); return false; // need hard reset } bool AuroraeFactory::supports(Ability ability) const { switch (ability) { case AbilityAnnounceButtons: case AbilityUsesAlphaChannel: case AbilityAnnounceAlphaChannel: case AbilityButtonMenu: case AbilityButtonSpacer: case AbilityExtendIntoClientArea: case AbilityButtonMinimize: case AbilityButtonMaximize: case AbilityButtonClose: case AbilityButtonAboveOthers: case AbilityButtonBelowOthers: case AbilityButtonShade: case AbilityButtonOnAllDesktops: case AbilityButtonHelp: case AbilityButtonApplicationMenu: case AbilityProvidesShadow: return true; // TODO: correct value from theme case AbilityTabbing: return false; case AbilityUsesBlurBehind: return true; default: return false; } } KDecoration *AuroraeFactory::createDecoration(KDecorationBridge *bridge) { AuroraeClient *client = new AuroraeClient(bridge, this); return client; } QList< KDecorationDefines::BorderSize > AuroraeFactory::borderSizes() const { return QList< BorderSize >() << BorderTiny << BorderNormal << BorderLarge << BorderVeryLarge << BorderHuge << BorderVeryHuge << BorderOversized; } QDeclarativeItem *AuroraeFactory::createQmlDecoration(Aurorae::AuroraeClient *client) { QDeclarativeContext *context = new QDeclarativeContext(m_engine->rootContext(), this); context->setContextProperty(QStringLiteral("decoration"), client); return qobject_cast< QDeclarativeItem* >(m_component->create(context)); } AuroraeFactory *AuroraeFactory::s_instance = NULL; /******************************************************* * Client *******************************************************/ AuroraeClient::AuroraeClient(KDecorationBridge *bridge, KDecorationFactory *factory) - : KDecorationUnstable(bridge, factory) + : KDecoration(bridge, factory) , m_view(NULL) , m_scene(new QGraphicsScene(this)) , m_item(AuroraeFactory::instance()->createQmlDecoration(this)) { connect(this, SIGNAL(keepAboveChanged(bool)), SIGNAL(keepAboveChangedWrapper())); connect(this, SIGNAL(keepBelowChanged(bool)), SIGNAL(keepBelowChangedWrapper())); connect(AuroraeFactory::instance(), SIGNAL(buttonsChanged()), SIGNAL(buttonsChanged())); connect(AuroraeFactory::instance(), SIGNAL(configChanged()), SIGNAL(configChanged())); connect(AuroraeFactory::instance(), SIGNAL(titleFontChanged()), SIGNAL(fontChanged())); connect(m_item, SIGNAL(alphaChanged()), SLOT(slotAlphaChanged())); connect(this, SIGNAL(appMenuAvailable()), SIGNAL(appMenuAvailableChanged())); connect(this, SIGNAL(appMenuUnavailable()), SIGNAL(appMenuAvailableChanged())); } AuroraeClient::~AuroraeClient() { if (m_item) { m_item->setParent(NULL); m_item->deleteLater(); } m_scene->setParent(NULL); m_scene->deleteLater(); m_view->setParent(NULL); m_view->deleteLater(); } void AuroraeClient::init() { m_scene->setItemIndexMethod(QGraphicsScene::NoIndex); // HACK: we need to add the GraphicsView as a child widget to a normal widget // the GraphicsView eats the mouse release event and by that kwin core starts to move // the decoration each time the decoration is clicked // therefore we use two widgets and inject an own mouse release event to the parent widget // when the graphics view eats a mouse event createMainWidget(); widget()->setAttribute(Qt::WA_TranslucentBackground); widget()->setAttribute(Qt::WA_NoSystemBackground); widget()->installEventFilter(this); m_view = new QGraphicsView(m_scene, widget()); m_view->setAttribute(Qt::WA_TranslucentBackground); m_view->setWindowFlags(Qt::X11BypassWindowManagerHint); m_view->setFrameShape(QFrame::NoFrame); m_view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); m_view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); m_view->setOptimizationFlags(QGraphicsView::DontSavePainterState); m_view->setViewportUpdateMode(QGraphicsView::BoundingRectViewportUpdate); QPalette pal = m_view->palette(); pal.setColor(m_view->backgroundRole(), Qt::transparent); m_view->setPalette(pal); QPalette pal2 = widget()->palette(); pal2.setColor(widget()->backgroundRole(), Qt::transparent); widget()->setPalette(pal2); if (m_item) m_scene->addItem(m_item); slotAlphaChanged(); AuroraeFactory::instance()->theme()->setCompositingActive(compositingActive()); } bool AuroraeClient::eventFilter(QObject *object, QEvent *event) { // we need to filter the wheel events on the decoration // QML does not yet provide a way to accept wheel events, this will change with Qt 5 // TODO: remove in KDE5 // see BUG: 304248 if (object != widget() || event->type() != QEvent::Wheel) { - return KDecorationUnstable::eventFilter(object, event); + return KDecoration::eventFilter(object, event); } QWheelEvent *wheel = static_cast(event); if (mousePosition(wheel->pos()) == PositionCenter) { titlebarMouseWheelOperation(wheel->delta()); return true; } return false; } void AuroraeClient::activeChange() { emit activeChanged(); } void AuroraeClient::captionChange() { emit captionChanged(); } void AuroraeClient::iconChange() { emit iconChanged(); } void AuroraeClient::desktopChange() { emit desktopChanged(); } void AuroraeClient::maximizeChange() { emit maximizeChanged(); } void AuroraeClient::resize(const QSize &s) { if (m_item) { m_item->setWidth(s.width()); m_item->setHeight(s.height()); } m_scene->setSceneRect(QRectF(QPoint(0, 0), s)); m_view->resize(s); widget()->resize(s); } void AuroraeClient::shadeChange() { emit shadeChanged(); } void AuroraeClient::borders(int &left, int &right, int &top, int &bottom) const { if (!m_item) { left = right = top = bottom = 0; return; } QObject *borders = NULL; if (maximizeMode() == MaximizeFull) { borders = m_item->findChild(QStringLiteral("maximizedBorders")); } else { borders = m_item->findChild(QStringLiteral("borders")); } sizesFromBorders(borders, left, right, top, bottom); } void AuroraeClient::padding(int &left, int &right, int &top, int &bottom) const { if (!m_item) { left = right = top = bottom = 0; return; } if (maximizeMode() == MaximizeFull) { left = right = top = bottom = 0; return; } sizesFromBorders(m_item->findChild(QStringLiteral("padding")), left, right, top, bottom); } void AuroraeClient::sizesFromBorders(const QObject *borders, int &left, int &right, int &top, int &bottom) const { if (!borders) { return; } left = borders->property("left").toInt(); right = borders->property("right").toInt(); top = borders->property("top").toInt(); bottom = borders->property("bottom").toInt(); } QSize AuroraeClient::minimumSize() const { return widget()->minimumSize(); } KDecorationDefines::Position AuroraeClient::mousePosition(const QPoint &point) const { // based on the code from deKorator int pos = PositionCenter; if (isShade()) { return Position(pos); } int borderLeft, borderTop, borderRight, borderBottom; borders(borderLeft, borderRight, borderTop, borderBottom); int paddingLeft, paddingTop, paddingRight, paddingBottom; padding(paddingLeft, paddingRight, paddingTop, paddingBottom); const bool maximized = maximizeMode() == MaximizeFull; int titleEdgeLeft, titleEdgeRight, titleEdgeTop, titleEdgeBottom; AuroraeFactory::instance()->theme()->titleEdges(titleEdgeLeft, titleEdgeTop, titleEdgeRight, titleEdgeBottom, maximized); switch (AuroraeFactory::instance()->theme()->decorationPosition()) { case DecorationTop: borderTop = titleEdgeTop; break; case DecorationLeft: borderLeft = titleEdgeLeft; break; case DecorationRight: borderRight = titleEdgeRight; break; case DecorationBottom: borderBottom = titleEdgeBottom; break; default: break; // nothing } if (point.x() >= (m_view->width() - borderRight - paddingRight)) { pos |= PositionRight; } else if (point.x() <= borderLeft + paddingLeft) { pos |= PositionLeft; } if (point.y() >= m_view->height() - borderBottom - paddingBottom) { pos |= PositionBottom; } else if (point.y() <= borderTop + paddingTop ) { pos |= PositionTop; } return Position(pos); } void AuroraeClient::reset(long unsigned int changed) { KDecoration::reset(changed); } void AuroraeClient::menuClicked() { showWindowMenu(QCursor::pos()); } void AuroraeClient::appMenuClicked() { showApplicationMenu(QCursor::pos()); } void AuroraeClient::toggleShade() { setShade(!isShade()); } void AuroraeClient::toggleKeepAbove() { setKeepAbove(!keepAbove()); } void AuroraeClient::toggleKeepBelow() { setKeepBelow(!keepBelow()); } bool AuroraeClient::isMaximized() const { return maximizeMode()==KDecorationDefines::MaximizeFull; } void AuroraeClient::titlePressed(int button, int buttons) { titlePressed(static_cast(button), static_cast(buttons)); } void AuroraeClient::titleReleased(int button, int buttons) { titleReleased(static_cast(button), static_cast(buttons)); } void AuroraeClient::titleMouseMoved(int button, int buttons) { titleMouseMoved(static_cast(button), static_cast(buttons)); } void AuroraeClient::titlePressed(Qt::MouseButton button, Qt::MouseButtons buttons) { const QPoint cursor = QCursor::pos(); QMouseEvent *event = new QMouseEvent(QEvent::MouseButtonPress, widget()->mapFromGlobal(cursor), cursor, button, buttons, Qt::NoModifier); processMousePressEvent(event); delete event; event = 0; } void AuroraeClient::titleReleased(Qt::MouseButton button, Qt::MouseButtons buttons) { const QPoint cursor = QCursor::pos(); QMouseEvent *event = new QMouseEvent(QEvent::MouseButtonRelease, widget()->mapFromGlobal(cursor), cursor, button, buttons, Qt::NoModifier); QApplication::sendEvent(widget(), event); delete event; event = 0; } void AuroraeClient::titleMouseMoved(Qt::MouseButton button, Qt::MouseButtons buttons) { const QPoint cursor = QCursor::pos(); QMouseEvent *event = new QMouseEvent(QEvent::MouseMove, widget()->mapFromGlobal(cursor), cursor, button, buttons, Qt::NoModifier); QApplication::sendEvent(widget(), event); delete event; event = 0; } void AuroraeClient::themeChanged() { m_scene->clear(); m_item = AuroraeFactory::instance()->createQmlDecoration(this); if (!m_item) { return; } m_item->setWidth(m_scene->sceneRect().width()); m_item->setHeight(m_scene->sceneRect().height()); m_scene->addItem(m_item); connect(m_item, SIGNAL(alphaChanged()), SLOT(slotAlphaChanged())); slotAlphaChanged(); } int AuroraeClient::doubleClickInterval() const { return QApplication::doubleClickInterval(); } void AuroraeClient::closeWindow() { - QMetaObject::invokeMethod(qobject_cast< KDecorationUnstable* >(this), "doCloseWindow", Qt::QueuedConnection); + QMetaObject::invokeMethod(qobject_cast< KDecoration* >(this), "doCloseWindow", Qt::QueuedConnection); } void AuroraeClient::doCloseWindow() { - KDecorationUnstable::closeWindow(); + KDecoration::closeWindow(); } void AuroraeClient::maximize(int button) { // a maximized window does not need to have a window decoration // in that case we need to delay handling by one cycle // BUG: 304870 - QMetaObject::invokeMethod(qobject_cast< KDecorationUnstable* >(this), + QMetaObject::invokeMethod(qobject_cast< KDecoration* >(this), "doMaximzie", Qt::QueuedConnection, Q_ARG(int, button)); } void AuroraeClient::doMaximzie(int button) { - KDecorationUnstable::maximize(static_cast(button)); + KDecoration::maximize(static_cast(button)); } void AuroraeClient::titlebarDblClickOperation() { // the double click operation can result in a window being maximized // see maximize - QMetaObject::invokeMethod(qobject_cast< KDecorationUnstable* >(this), "doTitlebarDblClickOperation", Qt::QueuedConnection); + QMetaObject::invokeMethod(qobject_cast< KDecoration* >(this), "doTitlebarDblClickOperation", Qt::QueuedConnection); } void AuroraeClient::doTitlebarDblClickOperation() { - KDecorationUnstable::titlebarDblClickOperation(); + KDecoration::titlebarDblClickOperation(); } QVariant AuroraeClient::readConfig(const QString &key, const QVariant &defaultValue) { KSharedConfigPtr config = KSharedConfig::openConfig(QStringLiteral("auroraerc")); return config->group(AuroraeFactory::instance()->currentThemeName()).readEntry(key, defaultValue); } void AuroraeClient::slotAlphaChanged() { if (!m_item) { setAlphaEnabled(false); return; } QVariant alphaProperty = m_item->property("alpha"); if (alphaProperty.isValid() && alphaProperty.canConvert()) { setAlphaEnabled(alphaProperty.toBool()); } else { // by default all Aurorae themes use the alpha channel setAlphaEnabled(true); } } QRegion AuroraeClient::region(KDecorationDefines::Region r) { if (r != ExtendedBorderRegion) { return QRegion(); } if (!m_item) { return QRegion(); } if (isMaximized()) { // empty region for maximized windows return QRegion(); } int left, right, top, bottom; left = right = top = bottom = 0; sizesFromBorders(m_item->findChild(QStringLiteral("extendedBorders")), left, right, top, bottom); if (top == 0 && right == 0 && bottom == 0 && left == 0) { // no extended borders return QRegion(); } int paddingLeft, paddingRight, paddingTop, paddingBottom; paddingLeft = paddingRight = paddingTop = paddingBottom = 0; padding(paddingLeft, paddingRight, paddingTop, paddingBottom); QRect rect = widget()->rect().adjusted(paddingLeft, paddingTop, -paddingRight, -paddingBottom); rect.translate(-paddingLeft, -paddingTop); return QRegion(rect.adjusted(-left, -top, right, bottom)).subtract(rect); } bool AuroraeClient::animationsSupported() const { if (!compositingActive()) { return false; } QPixmap pix(1,1); QPainter p(&pix); const bool raster = p.paintEngine()->type() == QPaintEngine::Raster; p.end(); return raster; } } // namespace Aurorae extern "C" { KDE_EXPORT KDecorationFactory *create_factory() { return Aurorae::AuroraeFactory::instance(); } KWIN_EXPORT int decoration_version() { return KWIN_DECORATION_API_VERSION; } } #include "aurorae.moc" diff --git a/kwin/clients/aurorae/src/aurorae.h b/kwin/clients/aurorae/src/aurorae.h index f11f4c9c97..0b0899a1b7 100644 --- a/kwin/clients/aurorae/src/aurorae.h +++ b/kwin/clients/aurorae/src/aurorae.h @@ -1,193 +1,193 @@ /******************************************************************** Copyright (C) 2009, 2010, 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 AURORAE_H #define AURORAE_H #include "themeconfig.h" #include #include class QDeclarativeComponent; class QDeclarativeEngine; class QDeclarativeItem; class QGraphicsSceneMouseEvent; class QGraphicsScene; class QGraphicsView; class KConfig; class KConfigGroup; namespace Aurorae { class AuroraeTheme; class AuroraeClient; class AuroraeFactory : public QObject, public KDecorationFactoryUnstable { Q_OBJECT public: ~AuroraeFactory(); static AuroraeFactory* instance(); bool reset(unsigned long changed); KDecoration *createDecoration(KDecorationBridge*); bool supports(Ability ability) const; virtual QList< BorderSize > borderSizes() const; AuroraeTheme *theme() const { return m_theme; } QDeclarativeItem *createQmlDecoration(AuroraeClient *client); const QString ¤tThemeName() const { return m_themeName; } private: enum EngineType { AuroraeEngine, QMLEngine }; AuroraeFactory(); void init(); void initAurorae(KConfig &conf, KConfigGroup &group); void initQML(const KConfigGroup& group); Q_SIGNALS: void buttonsChanged(); void titleFontChanged(); void configChanged(); private: static AuroraeFactory *s_instance; AuroraeTheme *m_theme; QDeclarativeEngine *m_engine; QDeclarativeComponent *m_component; EngineType m_engineType; QString m_themeName; }; -class AuroraeClient : public KDecorationUnstable +class AuroraeClient : public KDecoration { Q_OBJECT Q_PROPERTY(bool active READ isActive NOTIFY activeChanged) Q_PROPERTY(QString caption READ caption NOTIFY captionChanged) Q_PROPERTY(int desktop READ desktop WRITE setDesktop NOTIFY desktopChanged) Q_PROPERTY(QRect geometry READ geometry) Q_PROPERTY(int height READ height) Q_PROPERTY(QIcon icon READ icon NOTIFY iconChanged) Q_PROPERTY(bool closeable READ isCloseable CONSTANT) Q_PROPERTY(bool maximizeable READ isMaximizable CONSTANT) Q_PROPERTY(bool minimizeable READ isMinimizable CONSTANT) Q_PROPERTY(bool modal READ isModal) Q_PROPERTY(bool moveable READ isMovable CONSTANT) Q_PROPERTY(bool onAllDesktops READ isOnAllDesktops NOTIFY desktopChanged) Q_PROPERTY(bool preview READ isPreview CONSTANT) Q_PROPERTY(bool resizeable READ isResizable CONSTANT) Q_PROPERTY(bool setShade READ isSetShade NOTIFY shadeChanged) Q_PROPERTY(bool shade READ isShade WRITE setShade NOTIFY shadeChanged) Q_PROPERTY(bool shadeable READ isShadeable) Q_PROPERTY(bool keepAbove READ keepAbove WRITE setKeepAbove NOTIFY keepAboveChangedWrapper) Q_PROPERTY(bool keepBelow READ keepBelow WRITE setKeepBelow NOTIFY keepBelowChangedWrapper) Q_PROPERTY(bool maximized READ isMaximized NOTIFY maximizeChanged) Q_PROPERTY(bool providesContextHelp READ providesContextHelp) Q_PROPERTY(bool appMenu READ menuAvailable NOTIFY appMenuAvailableChanged) Q_PROPERTY(QRect transparentRect READ transparentRect) Q_PROPERTY(int width READ width) Q_PROPERTY(qulonglong windowId READ windowId CONSTANT) Q_PROPERTY(int doubleClickInterval READ doubleClickInterval) Q_PROPERTY(bool animationsSupported READ animationsSupported CONSTANT) // TODO: window tabs - they suck for dynamic features public: AuroraeClient(KDecorationBridge* bridge, KDecorationFactory* factory); virtual ~AuroraeClient(); virtual bool eventFilter(QObject *object, QEvent *event); virtual void activeChange(); virtual void borders(int& left, int& right, int& top, int& bottom) const; virtual void captionChange(); virtual void desktopChange(); virtual void iconChange(); virtual void init(); virtual void maximizeChange(); virtual QSize minimumSize() const; virtual Position mousePosition(const QPoint& p) const; virtual void resize(const QSize& s); virtual void shadeChange(); // optional overrides virtual void padding(int &left, int &right, int &top, int &bottom) const; virtual void reset(long unsigned int changed); bool isMaximized() const; int doubleClickInterval() const; bool animationsSupported() const; Q_INVOKABLE QVariant readConfig(const QString &key, const QVariant &defaultValue = QVariant()); Q_SIGNALS: void activeChanged(); void captionChanged(); void desktopChanged(); void iconChanged(); void maximizeChanged(); void shadeChanged(); void keepAboveChangedWrapper(); void keepBelowChangedWrapper(); void buttonsChanged(); /** * Signal emitted when the decoration's configuration might have changed. * A decoration could reload it's configuration when this signal is emitted. **/ void configChanged(); void fontChanged(); void appMenuAvailableChanged(); public Q_SLOTS: void menuClicked(); void appMenuClicked(); void toggleShade(); void toggleKeepAbove(); void toggleKeepBelow(); void titlePressed(int button, int buttons); void titleReleased(int button, int buttons); void titleMouseMoved(int button, int buttons); void titlePressed(Qt::MouseButton button, Qt::MouseButtons buttons); void titleReleased(Qt::MouseButton button, Qt::MouseButtons buttons); void titleMouseMoved(Qt::MouseButton button, Qt::MouseButtons buttons); void closeWindow(); void titlebarDblClickOperation(); void maximize(int button); QRegion region(KDecorationDefines::Region r); private Q_SLOTS: void themeChanged(); void doCloseWindow(); void doTitlebarDblClickOperation(); void doMaximzie(int button); void slotAlphaChanged(); private: void sizesFromBorders(const QObject *borders, int &left, int &right, int &top, int &bottom) const; QGraphicsView *m_view; QGraphicsScene *m_scene; QDeclarativeItem *m_item; }; } #endif diff --git a/kwin/clients/oxygen/oxygenclient.cpp b/kwin/clients/oxygen/oxygenclient.cpp index 23f95aa3d6..797d827066 100644 --- a/kwin/clients/oxygen/oxygenclient.cpp +++ b/kwin/clients/oxygen/oxygenclient.cpp @@ -1,2045 +1,2045 @@ ////////////////////////////////////////////////////////////////////////////// // oxygenclient.cpp // ------------------- // // Copyright (c) 2009 Hugo Pereira Da Costa // Copyright (c) 2006, 2007 Casper Boemann // Copyright (c) 2006, 2007 Riccardo Iaconelli // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to // deal in the Software without restriction, including without limitation the // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or // sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. ////////////////////////////////////////////////////////////////////////////// #include "oxygenclient.h" #include "oxygenclient.moc" #include "oxygenbutton.h" #include "oxygensizegrip.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Oxygen { //___________________________________________ Client::Client(KDecorationBridge *b, Factory *f): - KCommonDecorationUnstable(b, f), + KCommonDecoration(b, f), _factory( f ), _sizeGrip( 0 ), _glowAnimation( new Animation( 200, this ) ), _titleAnimationData( new TitleAnimationData( this ) ), _glowIntensity(0), _initialized( false ), _forceActive( false ), _mouseButton( Qt::NoButton ), _itemData( this ), _sourceItem( -1 ), _shadowAtom( 0 ) {} //___________________________________________ Client::~Client() { // delete sizegrip if any if( hasSizeGrip() ) deleteSizeGrip(); } //___________________________________________ QString Client::visibleName() const { return i18n("Oxygen"); } //___________________________________________ void Client::init() { // make sure valid configuration is set if( !_configuration ) _configuration = _factory->configuration( *this ); KCommonDecoration::init(); widget()->setAttribute(Qt::WA_NoSystemBackground ); widget()->setAutoFillBackground( false ); widget()->setAcceptDrops( true ); // setup glow animation _glowAnimation->setStartValue( glowBias() ); _glowAnimation->setEndValue( 1.0 ); _glowAnimation->setTargetObject( this ); _glowAnimation->setPropertyName( "glowIntensity" ); _glowAnimation->setEasingCurve( QEasingCurve::InOutQuad ); connect( _glowAnimation, SIGNAL(finished()), this, SLOT(clearForceActive()) ); // title animation data _titleAnimationData->initialize(); connect( _titleAnimationData, SIGNAL(pixmapsChanged()), SLOT(updateTitleRect()) ); // lists connect( _itemData.animation().data(), SIGNAL(finished()), this, SLOT(clearTargetItem()) ); // in case of preview, one wants to make the label used // for the central widget transparent. This allows one to have // the correct background (with gradient) rendered // Remark: this is minor (and safe) a hack. // This should be moved upstream (into kwin/lib/kdecoration) if( isPreview() ) { QList children( widget()->findChildren() ); foreach( QLabel* widget, children ) { widget->setAutoFillBackground( false ); } // also change shadow configuration size to something that fits in the preview list shadowCache().setShadowSize( QPalette::Active, 15 ); shadowCache().setShadowSize( QPalette::Inactive, 15 ); } setAlphaEnabled(!isMaximized()); _initialized = true; // first reset is needed to store Oxygen configuration reset(0); } //___________________________________________ void Client::reset( unsigned long changed ) { - KCommonDecorationUnstable::reset( changed ); + KCommonDecoration::reset( changed ); // update window mask when compositing is changed if( !_initialized ) return; if( changed & SettingCompositing ) { updateWindowShape(); widget()->update(); } _configuration = _factory->configuration( *this ); // glow animations _glowAnimation->setDuration( _configuration->shadowAnimationsDuration() ); // title transitions _titleAnimationData->setDuration( _configuration->titleAnimationsDuration() ); // tabs _itemData.setAnimationsEnabled( animationsEnabled() && _configuration->tabAnimationsEnabled() ); _itemData.animation().data()->setDuration( _configuration->tabAnimationsDuration() ); // reset title transitions _titleAnimationData->reset(); // should also update animations for buttons resetButtons(); // also reset tab buttons for( int index = 0; index < _itemData.count(); index++ ) { ClientGroupItemData& item( _itemData[index] ); if( item._closeButton ) { item._closeButton.data()->reset(0); } } // reset tab geometry _itemData.setDirty( true ); // handle size grip if( _configuration->drawSizeGrip() && _configuration->frameBorder() == Configuration::BorderNone ) { if( !hasSizeGrip() ) createSizeGrip(); } else if( hasSizeGrip() ) deleteSizeGrip(); // needs to remove shadow property on window since shadows are handled by the decoration removeShadowHint(); } //___________________________________________ bool Client::decorationBehaviour(DecorationBehaviour behaviour) const { switch (behaviour) { case DB_MenuClose: return _configuration->closeWindowFromMenuButton(); case DB_WindowMask: return false; default: return KCommonDecoration::decorationBehaviour(behaviour); } } //_________________________________________________________ KCommonDecorationButton *Client::createButton(::ButtonType type) { switch (type) { case MenuButton: return new Button(*this, i18n("Window Actions Menu"), ButtonMenu); case AppMenuButton: return new Button(*this, i18n("Application Menu"), ButtonApplicationMenu); case HelpButton: return new Button(*this, i18n("Help"), ButtonHelp); case MinButton: return new Button(*this, i18n("Minimize"), ButtonMin); case MaxButton: return new Button(*this, i18n("Maximize"), ButtonMax); case CloseButton: return new Button(*this, i18n("Close"), ButtonClose); case AboveButton: return new Button(*this, i18n("Keep Above Others"), ButtonAbove); case BelowButton: return new Button(*this, i18n("Keep Below Others"), ButtonBelow); case OnAllDesktopsButton: return new Button(*this, i18n("On All Desktops"), ButtonSticky); case ShadeButton: return new Button(*this, i18n("Shade Button"), ButtonShade); default: break; } return NULL; } //_________________________________________________________ QRegion Client::calcMask( void ) const { if( isMaximized() ) { return widget()->rect(); } const QRect frame( widget()->rect().adjusted( layoutMetric( LM_OuterPaddingLeft ), layoutMetric( LM_OuterPaddingTop ), -layoutMetric( LM_OuterPaddingRight ), -layoutMetric( LM_OuterPaddingBottom ) ) ); QRegion mask; if( _configuration->frameBorder() == Configuration::BorderNone && !isShade() ) { if( hideTitleBar() ) mask = QRegion(); else if( compositingActive() ) mask = QRegion(); else mask = helper().roundedMask( frame, 1, 1, 1, 0 ); } else { if( compositingActive() ) mask = QRegion(); else mask = helper().roundedMask( frame ); } return mask; } //___________________________________________ int Client::layoutMetric(LayoutMetric lm, bool respectWindowState, const KCommonDecorationButton *btn) const { const bool maximized( isMaximized() ); const bool shaded( isShade() ); const bool narrowSpacing( _configuration->useNarrowButtonSpacing() ); const int frameBorder( this->frameBorder() ); const int buttonSize( hideTitleBar() ? 0 : this->buttonSize() ); switch (lm) { case LM_BorderLeft: case LM_BorderRight: { int border( frameBorder ); if( respectWindowState && maximized ) { border = 0; } else if( _configuration->frameBorder() < Configuration::BorderTiny ) { border = 0; } else if( !compositingActive() && _configuration->frameBorder() == Configuration::BorderTiny ) { border = qMax( frameBorder, 3 ); } return border; } case LM_BorderBottom: { int border( frameBorder ); if( (respectWindowState && maximized) || shaded ) { border = 0; } else if( _configuration->frameBorder() >= Configuration::BorderNoSide ) { // for tiny border, the convention is to have a larger bottom area in order to // make resizing easier border = qMax(frameBorder, 4); } else if( _configuration->frameBorder() < Configuration::BorderTiny ) { border = 0; } else if( !compositingActive() && _configuration->frameBorder() == Configuration::BorderTiny ) { border = qMax( frameBorder, 3 ); } return border; } case LM_TitleEdgeTop: { int border = 0; if( _configuration->frameBorder() == Configuration::BorderNone && hideTitleBar() ) { border = 0; } else if( !( respectWindowState && maximized )) { border = TFRAMESIZE; } return border; } case LM_TitleEdgeBottom: { return 0; } case LM_TitleEdgeLeft: case LM_TitleEdgeRight: { int border = 0; if( !(respectWindowState && maximized) ) { border = 4; } return border; } case LM_TitleBorderLeft: case LM_TitleBorderRight: { int border = 5; // if title outline is to be drawn, one adds the space needed to // separate title and tab border. namely the same value if( _configuration->drawTitleOutline() ) border += border; return border; } case LM_ButtonWidth: case LM_ButtonHeight: case LM_TitleHeight: { return buttonSize; } case LM_ButtonSpacing: return narrowSpacing ? 1:3; case LM_ButtonMarginTop: return 0; // outer margin for shadow/glow case LM_OuterPaddingLeft: case LM_OuterPaddingRight: case LM_OuterPaddingTop: case LM_OuterPaddingBottom: if( maximized ) return 0; else return shadowCache().shadowSize(); default: return KCommonDecoration::layoutMetric(lm, respectWindowState, btn); } } //_________________________________________________________ QRect Client::defaultTitleRect( bool active ) const { QRect titleRect( this->titleRect().adjusted( 0, -layoutMetric( LM_TitleEdgeTop ), 0, 0 ) ); // when drawing title outline, shrink the rect so that it matches the actual caption size if( active && _configuration->drawTitleOutline() && isActive() ) { if( _configuration->titleAlignment() == Configuration::AlignCenterFullWidth ) { titleRect.setLeft( widget()->rect().left() + layoutMetric( LM_OuterPaddingLeft ) ); titleRect.setRight( widget()->rect().right() - layoutMetric( LM_OuterPaddingRight ) ); } const QRect textRect( titleBoundingRect( options()->font( true, false), titleRect, caption() ) ); titleRect.setLeft( textRect.left() - layoutMetric( LM_TitleBorderLeft ) ); titleRect.setRight( textRect.right() + layoutMetric( LM_TitleBorderRight ) ); } else { // buttons are properly accounted for in titleBoundingRect method titleRect.setLeft( widget()->rect().left() + layoutMetric( LM_OuterPaddingLeft ) ); titleRect.setRight( widget()->rect().right() - layoutMetric( LM_OuterPaddingRight ) ); } return titleRect; } //_________________________________________________________ QRect Client::titleBoundingRect( const QFont& font, QRect rect, const QString& caption ) const { // get title bounding rect QRect boundingRect( QFontMetrics( font ).boundingRect( rect, titleAlignment() | Qt::AlignVCenter, caption ) ); // adjust to make sure bounding rect // 1/ has same vertical alignment as original titleRect // 2/ does not exceeds available horizontal space boundingRect.setTop( rect.top() ); boundingRect.setBottom( rect.bottom() ); // check bounding rect against input rect boundRectTo( boundingRect, rect ); if( _configuration->titleAlignment() == Configuration::AlignCenterFullWidth ) { /* check bounding rect against max available space, for buttons this is not needed if centerTitleOnFullWidth flag is set to false, because it was already done before calling titleBoundingRect */ boundRectTo( boundingRect, titleRect() ); } return boundingRect; } //_________________________________________________________ void Client::boundRectTo( QRect& rect, const QRect& bound ) const { if( bound.left() > rect.left() ) { rect.moveLeft( bound.left() ); if( bound.right() < rect.right() ) { rect.setRight( bound.right() ); } } else if( bound.right() < rect.right() ) { rect.moveRight( bound.right() ); if( bound.left() > rect.left() ) { rect.setLeft( bound.left() ); } } return; } //_________________________________________________________ void Client::clearTargetItem( void ) { if( _itemData.animationType() == AnimationLeave ) { _itemData.setDirty( true ); } } //_________________________________________________________ void Client::updateItemBoundingRects( bool alsoUpdate ) { // make sure items are not animated _itemData.animate( AnimationNone ); // maximum available space const QRect titleRect( this->titleRect() ); // get tabs const int items( tabCount() ); // make sure item data have the correct number of items while( _itemData.count() < items ) _itemData.push_back( ClientGroupItemData() ); while( _itemData.count() > items ) { if( _itemData.back()._closeButton ) delete _itemData.back()._closeButton.data(); _itemData.pop_back(); } assert( !_itemData.isEmpty() ); // create buttons if( _itemData.count() == 1 ) { // remove button if( _itemData.front()._closeButton ) { delete _itemData.front()._closeButton.data(); } // set active rect _itemData.front()._activeRect = titleRect.adjusted( 0, -layoutMetric( LM_TitleEdgeTop ), 0, 0 ); } else { int left( titleRect.left() ); const int width( titleRect.width()/items ); for( int index = 0; index < _itemData.count(); index++ ) { ClientGroupItemData& item(_itemData[index]); // make sure button exists if( !item._closeButton ) { item._closeButton = ClientGroupItemData::ButtonPointer( new Button( *this, QStringLiteral("Close this tab"), ButtonItemClose ) ); item._closeButton.data()->show(); item._closeButton.data()->installEventFilter( this ); } // set active rect QRect local( QPoint( left, titleRect.top() ), QSize( width, titleRect.height() ) ); local.adjust( 0, -layoutMetric( LM_TitleEdgeTop ), 0, 0 ); item._activeRect = local; left += width; } } if( _itemData.count() == 1 ) { _itemData.front().reset( defaultTitleRect() ); } else { for( int index = 0; index < _itemData.count(); index++ ) { _itemData[index].reset( _itemData[index]._activeRect ); } } // button activity _itemData.updateButtonActivity( currentTabId() ); // reset buttons location _itemData.updateButtons( alsoUpdate ); _itemData.setDirty( false ); return; } //_________________________________________________________ QColor Client::titlebarTextColor(const QPalette &palette) const { if( glowIsAnimated() ) return KColorUtils::mix( titlebarTextColor( palette, false ), titlebarTextColor( palette, true ), glowIntensity() ); else return titlebarTextColor( palette, isActive() ); } //_________________________________________________________ void Client::renderWindowBackground( QPainter* painter, const QRect& rect, const QWidget* widget, const QPalette& palette ) const { // window background if( _configuration->blendStyle() == Configuration::BlendNone || ( _configuration->blendStyle() == Configuration::BlendFromStyle && !helper().hasBackgroundGradient( windowId() ) ) ) { painter->fillRect( rect, palette.color( QPalette::Window ) ); } else { int offset = layoutMetric( LM_OuterPaddingTop ); // radial gradient positionning const int height = hideTitleBar() ? 0:buttonSize(); if( isMaximized() ) offset -= 3; const QWidget* window( isPreview() ? this->widget() : widget->window() ); helper().renderWindowBackground(painter, rect, widget, window, palette, offset, height ); } // background pixmap if( isPreview() || helper().hasBackgroundPixmap( windowId() ) ) { int offset = layoutMetric( LM_OuterPaddingTop ); // radial gradient positionning const int height = hideTitleBar() ? 0:buttonSize(); if( isMaximized() ) offset -= 3; // background pixmap QPoint backgroundPixmapOffset( layoutMetric( LM_OuterPaddingLeft ) + layoutMetric( LM_BorderLeft ), 0 ); helper().setBackgroundPixmapOffset( backgroundPixmapOffset ); const QWidget* window( isPreview() ? this->widget() : widget->window() ); helper().renderBackgroundPixmap(painter, rect, widget, window, offset, height ); } } //_________________________________________________________ void Client::renderWindowBorder( QPainter* painter, const QRect& clipRect, const QWidget* widget, const QPalette& palette ) const { // get coordinates relative to the client area // this is annoying. One could use mapTo if this was taking const QWidget* and not // const QWidget* as argument. const QWidget* window = (isPreview()) ? this->widget() : widget->window(); const QWidget* w = widget; QPoint position( 0, 0 ); while ( w != window && !w->isWindow() && w != w->parentWidget() ) { position += w->geometry().topLeft(); w = w->parentWidget(); } // save painter if( clipRect.isValid() ) { painter->save(); painter->setClipRegion(clipRect,Qt::IntersectClip); } QRect r = (isPreview()) ? this->widget()->rect():window->rect(); r.adjust( layoutMetric( LM_OuterPaddingLeft ), layoutMetric( LM_OuterPaddingTop ), -layoutMetric( LM_OuterPaddingRight ), -layoutMetric( LM_OuterPaddingBottom ) ); r.adjust(0,0, 1, 1); // base color QColor color( palette.window().color() ); // add alpha channel if( _itemData.count() == 1 && glowIsAnimated() ) { color = helper().alphaColor( color, glowIntensity() ); } // title height const int titleHeight( layoutMetric( LM_TitleEdgeTop ) + layoutMetric( LM_TitleEdgeBottom ) + layoutMetric( LM_TitleHeight ) ); // make titlebar background darker for tabbed, non-outline window if( ( tabCount() >= 2 || _itemData.isAnimated() ) && !(_configuration->drawTitleOutline() && isActive() ) ) { const QPoint topLeft( r.topLeft()-position ); const QRect rect( topLeft, QSize( r.width(), titleHeight ) ); QLinearGradient lg( rect.topLeft(), rect.bottomLeft() ); lg.setColorAt( 0, helper().alphaColor( Qt::black, 0.05 ) ); lg.setColorAt( 1, helper().alphaColor( Qt::black, 0.10 ) ); painter->setBrush( lg ); painter->setPen( Qt::NoPen ); painter->drawRect( rect ); } // horizontal line { const int shadowSize = 7; const int height = shadowSize-3; const QPoint topLeft( r.topLeft()+QPoint(0,titleHeight-height)-position ); QRect rect( topLeft, QSize( r.width(), height ) ); // adjustements to cope with shadow size and outline border. rect.adjust( -shadowSize, 0, shadowSize-1, 0 ); if( _configuration->drawTitleOutline() && ( isActive() || glowIsAnimated() ) && !isMaximized() ) { if( _configuration->frameBorder() == Configuration::BorderTiny ) rect.adjust( 1, 0, -1, 0 ); else if( _configuration->frameBorder() > Configuration::BorderTiny ) rect.adjust( HFRAMESIZE-1, 0, -HFRAMESIZE+1, 0 ); } if( rect.isValid() ) { helper().slab( color, 0, shadowSize )->render( rect, painter, TileSet::Top ); } } if( _configuration->drawTitleOutline() && ( isActive() || glowIsAnimated() ) ) { // save old hints and turn off anti-aliasing const QPainter::RenderHints hints( painter->renderHints() ); painter->setRenderHint( QPainter::Antialiasing, false ); // save mask and frame to where // grey window background is to be rendered QRegion mask; QRect frame; // bottom line const int leftOffset = qMin( layoutMetric( LM_BorderLeft ), int(HFRAMESIZE) ); const int rightOffset = qMin( layoutMetric( LM_BorderRight ), int(HFRAMESIZE) ); if( _configuration->frameBorder() > Configuration::BorderNone ) { const int height = qMax( 0, layoutMetric( LM_BorderBottom ) - HFRAMESIZE ); const int width = r.width() - leftOffset - rightOffset - 1; const QRect rect( r.bottomLeft()-position + QPoint( leftOffset, -layoutMetric( LM_BorderBottom ) ), QSize( width, height ) ); if( height > 0 ) { mask += rect; frame |= rect; } const QColor shadow( helper().calcDarkColor( color ) ); painter->setPen( shadow ); painter->drawLine( rect.bottomLeft()+QPoint(-1,1), rect.bottomRight()+QPoint(1,1) ); } // left and right const int topOffset = titleHeight; const int bottomOffset = qMin( layoutMetric( LM_BorderBottom ), int(HFRAMESIZE) ); const int height = r.height() - topOffset - bottomOffset - 1; if( _configuration->frameBorder() >= Configuration::BorderTiny ) { const QColor shadow( helper().calcLightColor( color ) ); painter->setPen( shadow ); // left int width = qMax( 0, layoutMetric( LM_BorderLeft ) - HFRAMESIZE ); QRect rect( r.topLeft()-position + QPoint( layoutMetric( LM_BorderLeft ) - width, topOffset ), QSize( width, height ) ); if( width > 0 ) { mask += rect; frame |= rect; } painter->drawLine( rect.topLeft()-QPoint(1,0), rect.bottomLeft()-QPoint(1, 0) ); // right width = qMax( 0, layoutMetric( LM_BorderRight ) - HFRAMESIZE ); rect = QRect(r.topRight()-position + QPoint( -layoutMetric( LM_BorderRight ), topOffset ), QSize( width, height )); if( width > 0 ) { mask += rect; frame |= rect; } painter->drawLine( rect.topRight()+QPoint(1,0), rect.bottomRight()+QPoint(1, 0) ); } // restore old hints painter->setRenderHints( hints ); // in preview mode also adds center square if( isPreview() ) { const QRect rect( r.topLeft()-position + QPoint( layoutMetric( LM_BorderLeft ), topOffset ), QSize(r.width()-layoutMetric( LM_BorderLeft )-layoutMetric( LM_BorderRight ),height) ); mask += rect; frame |= rect; } // paint if( !mask.isEmpty() ) { painter->setClipRegion( mask, Qt::IntersectClip); renderWindowBackground(painter, frame, widget, palette ); } } // restore painter if( clipRect.isValid() ) { painter->restore(); } } //_________________________________________________________ void Client::renderSeparator( QPainter* painter, const QRect& clipRect, const QWidget* widget, const QColor& color ) const { const QWidget* window = (isPreview()) ? this->widget() : widget->window(); // get coordinates relative to the client area // this is annoying. One could use mapTo if this was taking const QWidget* and not // const QWidget* as argument. QPoint position( 0, 0 ); { const QWidget* w = widget; while ( w != window && !w->isWindow() && w != w->parentWidget() ) { position += w->geometry().topLeft(); w = w->parentWidget(); } } // setup painter if (clipRect.isValid()) { painter->save(); painter->setClipRegion(clipRect,Qt::IntersectClip); } QRect r = (isPreview()) ? this->widget()->rect():window->rect(); r.adjust( layoutMetric( LM_OuterPaddingLeft ), layoutMetric( LM_OuterPaddingTop ), -layoutMetric( LM_OuterPaddingRight ), -layoutMetric( LM_OuterPaddingBottom ) ); // dimensions const int titleHeight = layoutMetric(LM_TitleHeight); const int titleTop = layoutMetric(LM_TitleEdgeTop) + r.top(); // set color QColor local( color ); if( glowIsAnimated() && _configuration->separatorMode() != Configuration::SeparatorAlways ) { local = helper().alphaColor( color, glowIntensity() ); } // render helper().drawSeparator( painter, QRect(r.top(), titleTop+titleHeight-1.5, r.width(), 2).translated( -position ), local, Qt::Horizontal); if (clipRect.isValid()) { painter->restore(); } } //_________________________________________________________ void Client::renderTitleOutline( QPainter* painter, const QRect& rect, const QPalette& palette ) const { // center (for active windows only) { painter->save(); QRect adjustedRect( rect.adjusted( 1, 1, -1, 1 ) ); // prepare painter mask QRegion mask( adjustedRect.adjusted( 1, 0, -1, 0 ) ); mask += adjustedRect.adjusted( 0, 1, 0, 0 ); painter->setClipRegion( mask, Qt::IntersectClip ); // draw window background renderWindowBackground(painter, adjustedRect, widget(), palette ); painter->restore(); } // shadow const int shadowSize( 7 ); const int offset( -3 ); const int voffset( 5-shadowSize ); const QRect adjustedRect( rect.adjusted(offset, voffset, -offset, shadowSize) ); QColor color( palette.color( widget()->backgroundRole() ) ); // add alpha channel if( _itemData.count() == 1 && glowIsAnimated() ) { color = helper().alphaColor( color, glowIntensity() ); } // render slab helper().slab( color, 0, shadowSize )->render( adjustedRect, painter, TileSet::Tiles(TileSet::Top|TileSet::Left|TileSet::Right) ); } //_________________________________________________________ void Client::renderTitleText( QPainter* painter, const QRect& rect, const QColor& color, const QColor& contrast ) const { if( !_titleAnimationData->isValid() ) { // contrast pixmap _titleAnimationData->reset( rect, renderTitleText( rect, caption(), color ), renderTitleText( rect, caption(), contrast ) ); } if( _titleAnimationData->isDirty() ) { // clear dirty flags _titleAnimationData->setDirty( false ); // finish current animation if running if( _titleAnimationData->isAnimated() ) { _titleAnimationData->finishAnimation(); } if( !_titleAnimationData->isLocked() ) { // set pixmaps _titleAnimationData->setPixmaps( rect, renderTitleText( rect, caption(), color ), renderTitleText( rect, caption(), contrast ) ); _titleAnimationData->startAnimation(); renderTitleText( painter, rect, color, contrast ); } else if( !caption().isEmpty() ) { renderTitleText( painter, rect, caption(), color, contrast ); } // lock animations (this must be done whether or not // animation was actually started, in order to extend locking // every time title get changed too rapidly _titleAnimationData->lockAnimations(); } else if( _titleAnimationData->isAnimated() ) { if( isMaximized() ) painter->translate( 0, 2 ); if( !_titleAnimationData->contrastPixmap().isNull() ) { painter->translate( 0, 1 ); painter->drawPixmap( rect.topLeft(), _titleAnimationData->contrastPixmap() ); painter->translate( 0, -1 ); } painter->drawPixmap( rect.topLeft(), _titleAnimationData->pixmap() ); if( isMaximized() ) painter->translate( 0, -2 ); } else if( !caption().isEmpty() ) { renderTitleText( painter, rect, caption(), color, contrast ); } } //_______________________________________________________________________ void Client::renderTitleText( QPainter* painter, const QRect& rect, const QString& caption, const QColor& color, const QColor& contrast, bool elide ) const { const Qt::Alignment alignment( titleAlignment() | Qt::AlignVCenter ); const QString local( elide ? QFontMetrics( painter->font() ).elidedText( caption, Qt::ElideRight, rect.width() ):caption ); // translate title down in case of maximized window if( isMaximized() ) painter->translate( 0, 2 ); if( contrast.isValid() ) { painter->setPen( contrast ); painter->translate( 0, 1 ); painter->drawText( rect, alignment, local ); painter->translate( 0, -1 ); } painter->setPen( color ); painter->drawText( rect, alignment, local ); // translate back if( isMaximized() ) painter->translate( 0, -2 ); } //_______________________________________________________________________ QPixmap Client::renderTitleText( const QRect& rect, const QString& caption, const QColor& color, bool elide ) const { if( !rect.isValid() ) return QPixmap(); QPixmap out( rect.size() ); out.fill( Qt::transparent ); if( caption.isEmpty() || !color.isValid() ) return out; QPainter painter( &out ); painter.setFont( options()->font(isActive(), false) ); const Qt::Alignment alignment( titleAlignment() | Qt::AlignVCenter ); const QString local( elide ? QFontMetrics( painter.font() ).elidedText( caption, Qt::ElideRight, rect.width() ):caption ); painter.setPen( color ); painter.drawText( out.rect(), alignment, local ); painter.end(); return out; } //_______________________________________________________________________ void Client::renderItem( QPainter* painter, int index, const QPalette& palette ) { const ClientGroupItemData& item( _itemData[index] ); // see if tag is active const int itemCount( _itemData.count() ); // check item bounding rect if( !item._boundingRect.isValid() ) return; // create rect in which text is to be drawn QRect textRect( item._boundingRect.adjusted( 0, layoutMetric( LM_TitleEdgeTop )-1, 0, -1 ) ); // add extra space needed for title outline if( itemCount > 1 || _itemData.isAnimated() ) { textRect.adjust( layoutMetric( LM_TitleBorderLeft ), 0, -layoutMetric(LM_TitleBorderRight), 0 ); } // add extra space for the button if( itemCount > 1 && item._closeButton && item._closeButton.data()->isVisible() ) { textRect.adjust( 0, 0, - buttonSize() - layoutMetric(LM_TitleEdgeRight), 0 ); } // check if current item is active const bool active( tabId(index) == currentTabId() ); // get current item caption and update text rect const QString caption( itemCount == 1 ? this->caption() : this->caption(index) ); if( _configuration->titleAlignment() != Configuration::AlignCenterFullWidth ) { boundRectTo( textRect, titleRect() ); } // adjust textRect textRect = titleBoundingRect( painter->font(), textRect, caption ); // title outline if( itemCount == 1 ) { // no title outline if the window caption is empty if( !caption.trimmed().isEmpty() ) { if( _itemData.isAnimated() ) { renderTitleOutline( painter, item._boundingRect, palette ); } else if( (isActive()||glowIsAnimated()) && _configuration->drawTitleOutline() ) { // adjusts boundingRect accordingly QRect boundingRect( item._boundingRect ); boundingRect.setLeft( textRect.left() - layoutMetric( LM_TitleBorderLeft ) ); boundingRect.setRight( textRect.right() + layoutMetric( LM_TitleBorderRight ) ); // render bounding rect around it with extra margins renderTitleOutline( painter, boundingRect, palette ); } } } else if( active ) { // in multiple tabs render title outline in all cases renderTitleOutline( painter, item._boundingRect, palette ); } // render text if( active || itemCount == 1 ) { // for active tab, current caption is "merged" with old caption, if any renderTitleText( painter, textRect, titlebarTextColor( palette ), titlebarContrastColor( palette ) ); } else { QColor background( backgroundPalette( widget(), palette ).color( widget()->window()->backgroundRole() ) ); // add extra shade (as used in renderWindowBorder if( !( isActive() && _configuration->drawTitleOutline() ) ) { background = KColorUtils::mix( background, Qt::black, 0.10 ); } // otherwise current caption is rendered directly renderTitleText( painter, textRect, caption, titlebarTextColor( backgroundPalette( widget(), palette ), false ), titlebarContrastColor( background ) ); } // render separators between inactive tabs if( !( active || itemCount == 1 ) && item._closeButton && item._closeButton.data()->isVisible() ) { // separators // draw Left separator const QColor color( backgroundPalette( widget(), palette ).window().color() ); const bool isFirstItem( index == 0 || (index == 1 && !_itemData[0]._boundingRect.isValid() ) ); if( !active && ( ( isFirstItem && buttonsLeftWidth() > 0 ) || _itemData.isTarget( index ) ) ) { const QRect local( item._boundingRect.topLeft()+QPoint(0,2), QSize( 2, item._boundingRect.height()-3 ) ); helper().drawSeparator( painter, local, color, Qt::Vertical); } // draw right separator if( ( index == itemCount-1 && buttonsRightWidth() > 0 ) || ( index+1 < itemCount && ( _itemData.isTarget( index+1 ) || !( tabId(index+1) == currentTabId() && _itemData[index+1]._boundingRect.isValid() ) ) ) ) { const QRect local( item._boundingRect.topRight()+QPoint(0,2), QSize( 2, item._boundingRect.height()-3 ) ); helper().drawSeparator( painter, local, color, Qt::Vertical); } } } //_______________________________________________________________________ void Client::renderTargetRect( QPainter* p, const QPalette& palette ) { if( _itemData.targetRect().isNull() || _itemData.isAnimationRunning() ) return; const QColor color = palette.color(QPalette::Highlight); p->setPen(KColorUtils::mix(color, palette.color(QPalette::Active, QPalette::WindowText))); p->setBrush( helper().alphaColor( color, 0.5 ) ); p->drawRect( QRectF(_itemData.targetRect()).adjusted( 4.5, 2.5, -4.5, -2.5 ) ); } //_______________________________________________________________________ void Client::renderCorners( QPainter* painter, const QRect& frame, const QPalette& palette ) const { const QColor color( backgroundColor( widget(), palette ) ); QLinearGradient lg = QLinearGradient(0, -0.5, 0, qreal( frame.height() )+0.5); lg.setColorAt(0.0, helper().calcLightColor( helper().backgroundTopColor(color) )); lg.setColorAt(0.51, helper().backgroundBottomColor(color) ); lg.setColorAt(1.0, helper().backgroundBottomColor(color) ); painter->setPen( QPen( lg, 1 ) ); painter->setBrush( Qt::NoBrush ); painter->drawRoundedRect( QRectF( frame ).adjusted( 0.5, 0.5, -0.5, -0.5 ), 3.5, 3.5 ); } //_______________________________________________________________________ void Client::renderFloatFrame( QPainter* painter, const QRect& frame, const QPalette& palette ) const { // shadow and resize handles if( !isMaximized() ) { if( _configuration->frameBorder() >= Configuration::BorderTiny ) { helper().drawFloatFrame( painter, frame, backgroundColor( widget(), palette ), !compositingActive(), isActive() && shadowCache().isEnabled( QPalette::Active ), KDecoration::options()->color(ColorTitleBar) ); } else { // for small borders, use a frame that matches the titlebar only const QRect local( frame.topLeft(), QSize( frame.width(), layoutMetric(LM_TitleHeight) + layoutMetric(LM_TitleEdgeTop) ) ); helper().drawFloatFrame( painter, local, backgroundColor( widget(), palette ), false, isActive() && shadowCache().isEnabled( QPalette::Active ), KDecoration::options()->color(ColorTitleBar) ); } } else if( isShade() ) { // for shaded maximized windows adjust frame and draw the bottom part of it helper().drawFloatFrame( painter, frame, backgroundColor( widget(), palette ), !( compositingActive() || _configuration->frameBorder() == Configuration::BorderNone ), isActive(), KDecoration::options()->color(ColorTitleBar), TileSet::Bottom ); } } //____________________________________________________________________________ void Client::renderDots( QPainter* painter, const QRect& frame, const QColor& color ) const { if( _configuration->frameBorder() >= Configuration::BorderTiny ) { // dimensions int x,y,w,h; frame.getRect(&x, &y, &w, &h); if( isResizable() && !isShade() && !isMaximized() ) { // Draw right side 3-dots resize handles const int cenY = (h / 2 + y) ; const int posX = (w + x - 3); helper().renderDot( painter, QPoint(posX, cenY - 3), color); helper().renderDot( painter, QPoint(posX, cenY), color); helper().renderDot( painter, QPoint(posX, cenY + 3), color); } // Draw bottom-right cornet 3-dots resize handles if( isResizable() && !isShade() && !_configuration->drawSizeGrip() ) { painter->save(); painter->translate(x + w-9, y + h-9); helper().renderDot( painter, QPoint(2, 6), color); helper().renderDot( painter, QPoint(5, 5), color); helper().renderDot( painter, QPoint(6, 2), color); painter->restore(); } } } //_________________________________________________________ void Client::activeChange( void ) { - KCommonDecorationUnstable::activeChange(); + KCommonDecoration::activeChange(); _itemData.setDirty( true ); // reset animation if( shadowAnimationsEnabled() ) { _glowAnimation->setDirection( isActive() ? Animation::Forward : Animation::Backward ); if(!glowIsAnimated()) { _glowAnimation->start(); } } // update size grip so that it gets the right color // also make sure it is remaped to from z stack, // unless hidden if( hasSizeGrip() && !(isShade() || isMaximized() )) { sizeGrip().activeChange(); sizeGrip().update(); } } //_________________________________________________________ void Client::maximizeChange( void ) { if( hasSizeGrip() ) sizeGrip().setVisible( !( isShade() || isMaximized() ) ); setAlphaEnabled(!isMaximized()); - KCommonDecorationUnstable::maximizeChange(); + KCommonDecoration::maximizeChange(); } //_________________________________________________________ void Client::shadeChange( void ) { if( hasSizeGrip() ) sizeGrip().setVisible( !( isShade() || isMaximized() ) ); - KCommonDecorationUnstable::shadeChange(); + KCommonDecoration::shadeChange(); } //_________________________________________________________ void Client::captionChange( void ) { - KCommonDecorationUnstable::captionChange(); + KCommonDecoration::captionChange(); _itemData.setDirty( true ); if( titleAnimationsEnabled() ) { _titleAnimationData->setDirty( true ); } } //_________________________________________________________ QPalette Client::backgroundPalette( const QWidget* widget, QPalette palette ) const { if( _configuration->drawTitleOutline() ) { if( glowIsAnimated() && !isForcedActive() ) { const QColor inactiveColor( backgroundColor( widget, palette, false ) ); const QColor activeColor( backgroundColor( widget, palette, true ) ); const QColor mixed( KColorUtils::mix( inactiveColor, activeColor, glowIntensity() ) ); palette.setColor( QPalette::Window, mixed ); palette.setColor( QPalette::Button, mixed ); } else if( isActive() || isForcedActive() ) { const QColor color = options()->color( KDecorationDefines::ColorTitleBar, true ); palette.setColor( QPalette::Window, color ); palette.setColor( QPalette::Button, color ); } } return palette; } //_________________________________________________________ QColor Client::backgroundColor( const QWidget*, QPalette palette, bool active ) const { return ( _configuration->drawTitleOutline() && active ) ? options()->color( KDecorationDefines::ColorTitleBar, true ): palette.color( QPalette::Window ); } //_________________________________________________________ QString Client::defaultButtonsLeft() const { return KCommonDecoration::defaultButtonsLeft(); } //_________________________________________________________ QString Client::defaultButtonsRight() const { return QStringLiteral("HIAX"); } //________________________________________________________________ void Client::updateWindowShape() { if(isMaximized()) clearMask(); else setMask( calcMask() ); } //______________________________________________________________________________ bool Client::eventFilter( QObject* object, QEvent* event ) { // all dedicated event filtering is here to handle multiple tabs. bool state = false; switch( event->type() ) { case QEvent::Show: if( widget() == object ) { _itemData.setDirty( true ); } break; case QEvent::MouseButtonPress: if( widget() == object ) { state = mousePressEvent( static_cast< QMouseEvent* >( event ) ); } break; case QEvent::MouseButtonRelease: if( widget() == object ) state = mouseReleaseEvent( static_cast< QMouseEvent* >( event ) ); else if( Button *btn = qobject_cast< Button* >( object ) ) { QMouseEvent* mouseEvent( static_cast< QMouseEvent* >( event ) ); if( mouseEvent->button() == Qt::LeftButton && btn->rect().contains( mouseEvent->pos() ) ) { state = closeItem( btn ); } } break; case QEvent::MouseMove: state = mouseMoveEvent( static_cast< QMouseEvent* >( event ) ); break; case QEvent::DragEnter: if( widget() == object ) { state = dragEnterEvent( static_cast< QDragEnterEvent* >( event ) ); } break; case QEvent::DragMove: if( widget() == object ) { state = dragMoveEvent( static_cast< QDragMoveEvent* >( event ) ); } break; case QEvent::DragLeave: if( widget() == object ) { state = dragLeaveEvent( static_cast< QDragLeaveEvent* >( event ) ); } break; case QEvent::Drop: if( widget() == object ) { state = dropEvent( static_cast< QDropEvent* >( event ) ); } break; default: break; } - return state || KCommonDecorationUnstable::eventFilter( object, event ); + return state || KCommonDecoration::eventFilter( object, event ); } //_________________________________________________________ void Client::resizeEvent( QResizeEvent* event ) { // prepare item data updates _itemData.setDirty( true ); // mark title animation as dirty if( event->oldSize().width() != event->size().width() ) { _titleAnimationData->setDirty( true ); } // resize backing store pixmap if( !compositingActive() ) { _pixmap = QPixmap( event->size() ); } // base class implementation - KCommonDecorationUnstable::resizeEvent( event ); + KCommonDecoration::resizeEvent( event ); } //_________________________________________________________ void Client::paintBackground( QPainter& painter ) const { if( !compositingActive() ) { painter.drawPixmap( QPoint(), _pixmap ); } } //_________________________________________________________ QRegion Client::region( KDecorationDefines::Region r ) { // return empty region for anything but extended borders, when enabled if( !( r == KDecorationDefines::ExtendedBorderRegion && configuration()->useExtendedWindowBorders() ) ) { return QRegion(); } // return empty region for maximized windows if( isMaximized() ) return QRegion(); // return 3 pixels extended borders for sides that have no visible borders // also add the invisible pixels at the masked rounded corners, in non compositing mode if( configuration()->frameBorder() <= Configuration::BorderNoSide || !compositingActive() ) { QRect rect = widget()->rect().adjusted( layoutMetric( LM_OuterPaddingLeft ), layoutMetric( LM_OuterPaddingTop ), - layoutMetric( LM_OuterPaddingRight ), - layoutMetric( LM_OuterPaddingBottom ) ); rect.translate( -layoutMetric( LM_OuterPaddingLeft ), -layoutMetric( LM_OuterPaddingTop ) ); // mask QRegion mask( calcMask() ); if( mask.isEmpty() ) mask = rect; else mask.translate( -layoutMetric( LM_OuterPaddingLeft ), -layoutMetric( LM_OuterPaddingTop ) ); // only return non-empty region on the sides for which there is no border if( configuration()->frameBorder() == Configuration::BorderNone ) return QRegion( rect.adjusted( -3, 0, 3, 3 ) ) - mask; else if( configuration()->frameBorder() == Configuration::BorderNoSide ) return QRegion( rect.adjusted( -3, 0, 3, 0 ) ) - mask; else if( !compositingActive() ) return QRegion( rect ) - mask; } // fall back return QRegion(); } //_________________________________________________________ void Client::paintEvent( QPaintEvent* event ) { // factory if(!( _initialized && _factory->initialized() ) ) return; if( compositingActive() ) { QPainter painter(widget()); painter.setRenderHint(QPainter::Antialiasing); painter.setClipRegion( event->region() ); paint( painter ); // update buttons QList buttons( widget()->findChildren() ); foreach( Button* button, buttons ) { if( ( button->isVisible() || isPreview() ) && event->rect().intersects( button->geometry() ) ) { painter.save(); painter.setViewport( button->geometry() ); painter.setWindow( button->rect() ); button->paint( painter ); painter.restore(); } } } else { { // update backing store pixmap QPainter painter( &_pixmap ); painter.setRenderHint(QPainter::Antialiasing); painter.setClipRegion( event->region() ); paint( painter ); } QPainter painter( widget() ); painter.setClipRegion( event->region() ); painter.drawPixmap( QPoint(), _pixmap ); // update buttons QList buttons( widget()->findChildren() ); foreach( Button* button, buttons ) { if( event->rect().intersects( button->geometry() ) ) { button->update(); } } } } //_________________________________________________________ void Client::paint( QPainter& painter ) { // palette QPalette palette = widget()->palette(); palette.setCurrentColorGroup( (isActive() ) ? QPalette::Active : QPalette::Inactive ); // define frame QRect frame = widget()->rect(); // base color QColor color = palette.window().color(); // draw shadows if( compositingActive() && shadowCache().shadowSize() > 0 && !isMaximized() ) { TileSet *tileSet( 0 ); const ShadowCache::Key key( this->key() ); if( shadowCache().isEnabled( QPalette::Active ) && glowIsAnimated() && !isForcedActive() ) { tileSet = shadowCache().tileSet( key, glowIntensity() ); } else { tileSet = shadowCache().tileSet( key ); } tileSet->render( frame, &painter, TileSet::Ring); } // adjust frame frame.adjust( layoutMetric(LM_OuterPaddingLeft), layoutMetric(LM_OuterPaddingTop), -layoutMetric(LM_OuterPaddingRight), -layoutMetric(LM_OuterPaddingBottom) ); // adjust mask if( compositingActive() || isPreview() ) { if( isMaximized() ) { painter.setClipRect( frame, Qt::IntersectClip ); } else { // multipliers const int left = 1; const int right = 1; const int top = 1; int bottom = 1; // disable bottom corners when border frame is too small and window is not shaded if( _configuration->frameBorder() == Configuration::BorderNone && !isShade() ) bottom = 0; QRegion mask( helper().roundedMask( frame, left, right, top, bottom ) ); renderCorners( &painter, frame, palette ); painter.setClipRegion( mask, Qt::IntersectClip ); } } // make sure ItemData and tabList are synchronized /* this needs to be done before calling RenderWindowBorder since some painting in there depend on the clientGroups state */ if( _itemData.isDirty() || _itemData.count() != tabCount() ) { updateItemBoundingRects( false ); } // window background renderWindowBackground( &painter, frame, widget(), backgroundPalette( widget(), palette ) ); // window border (for title outline) if( hasTitleOutline() ) renderWindowBorder( &painter, frame, widget(), palette ); // clipping if( compositingActive() ) { painter.setClipping(false); frame.adjust(-1,-1, 1, 1); } // float frame renderFloatFrame( &painter, frame, palette ); // resize handles renderDots( &painter, frame, backgroundColor( widget(), palette ) ); if( !hideTitleBar() ) { // title bounding rect painter.setFont( options()->font(isActive(), false) ); // draw ClientGroupItems const int itemCount( _itemData.count() ); for( int i = 0; i < itemCount; i++ ) renderItem( &painter, i, palette ); // draw target rect renderTargetRect( &painter, widget()->palette() ); // separator if( itemCount == 1 && !_itemData.isAnimated() && drawSeparator() ) { renderSeparator(&painter, frame, widget(), color ); } } } //_____________________________________________________________ bool Client::mousePressEvent( QMouseEvent* event ) { const QPoint point = event->pos(); if( tabIndexAt( point ) < 0 ) return false; _dragPoint = point; _mouseButton = event->button(); bool accepted( false ); if( buttonToWindowOperation( _mouseButton ) == TabDragOp ) { accepted = true; } else if( buttonToWindowOperation( _mouseButton ) == OperationsOp ) { QPoint point = event->pos(); const int clickedIndex( tabIndexAt( point ) ); _mouseButton = Qt::NoButton; if ( tabIndexAt( point ) > -1) { showWindowMenu( widget()->mapToGlobal( event->pos() ), tabId(clickedIndex) ); } accepted = true; // displayClientMenu can possibly destroy the deco... } return accepted; } //_____________________________________________________________ bool Client::mouseReleaseEvent( QMouseEvent* event ) { bool accepted( false ); if( _mouseButton == event->button() && buttonToWindowOperation( _mouseButton ) != OperationsOp ) { const QPoint point = event->pos(); const long visibleItem = currentTabId(); const int clickedIndex( tabIndexAt( point ) ); if( clickedIndex >= 0 && visibleItem != tabId(clickedIndex) ) { setCurrentTab( tabId(clickedIndex) ); setForceActive( true ); accepted = true; } } _mouseButton = Qt::NoButton; return accepted; } //_____________________________________________________________ bool Client::mouseMoveEvent( QMouseEvent* event ) { // check button and distance to drag point if( hideTitleBar() || _mouseButton == Qt::NoButton || ( event->pos() - _dragPoint ).manhattanLength() <= QApplication::startDragDistance() ) { return false; } bool accepted( false ); if( buttonToWindowOperation( _mouseButton ) == TabDragOp ) { const QPoint point = event->pos(); const int clickedIndex( tabIndexAt( point ) ); if( clickedIndex < 0 ) return false; _titleAnimationData->reset(); QDrag *drag = new QDrag( widget() ); QMimeData *groupData = new QMimeData(); groupData->setData( tabDragMimeType(), QByteArray().setNum( (qint64) tabId(clickedIndex) ) ); drag->setMimeData( groupData ); _sourceItem = tabIndexAt( _dragPoint ); // get tab geometry QRect geometry( _itemData[clickedIndex]._boundingRect ); // remove space used for buttons if( _itemData.count() > 1 ) { geometry.adjust( 0, 0, - buttonSize() - layoutMetric(LM_TitleEdgeRight), 0 ); } else if( !( isActive() && _configuration->drawTitleOutline() ) ) { geometry.adjust( buttonsLeftWidth() + layoutMetric( LM_TitleEdgeLeft ) , 0, -( buttonsRightWidth() + layoutMetric( LM_TitleEdgeRight )), 0 ); } // adjust geometry to include shadow size const int shadowSize( shadowCache().shadowSize() ); const bool drawShadow( compositingActive() && KStyle::customStyleHint( QStringLiteral("SH_ArgbDndWindow"), widget() ) && shadowSize > 0 ); if( drawShadow ) { geometry.adjust( -shadowSize, -shadowSize, shadowSize, shadowSize ); } // compute pixmap and assign drag->setPixmap( itemDragPixmap( clickedIndex, geometry, drawShadow ) ); // note: the pixmap is moved just above the pointer on purpose // because overlapping pixmap and pointer slows down the pixmap a lot. QPoint hotSpot( QPoint( event->pos().x() - geometry.left(), -1 ) ); if( drawShadow ) hotSpot += QPoint( 0, shadowSize ); // make sure the horizontal hotspot position is not too far away (more than 1px) // from the pixmap if( hotSpot.x() < -1 ) hotSpot.setX(-1); if( hotSpot.x() > geometry.width() ) hotSpot.setX( geometry.width() ); drag->setHotSpot( hotSpot ); _dragStartTimer.start( 50, this ); drag->exec( Qt::MoveAction ); // detach tab from window if( drag->target() == 0 && _itemData.count() > 1 ) { _itemData.setDirty( true ); untab( tabId(_sourceItem), widget()->frameGeometry().adjusted( layoutMetric( LM_OuterPaddingLeft ), layoutMetric( LM_OuterPaddingTop ), -layoutMetric( LM_OuterPaddingRight ), -layoutMetric( LM_OuterPaddingBottom ) ).translated( QCursor::pos() - event->pos() + QPoint( layoutMetric( LM_OuterPaddingLeft ), layoutMetric( LM_OuterPaddingTop ))) ); } // reset button _mouseButton = Qt::NoButton; accepted = true; } return accepted; } //_____________________________________________________________ bool Client::dragEnterEvent( QDragEnterEvent* event ) { // check if drag enter is allowed if( !event->mimeData()->hasFormat( tabDragMimeType() ) || hideTitleBar() ) return false; // accept event event->acceptProposedAction(); // animate if( event->source() != widget() ) { _itemData.animate( AnimationEnter, tabIndexAt( event->pos(), true ) ); } else if( _itemData.count() > 1 ) { _itemData.animate( AnimationEnter|AnimationSameTarget, tabIndexAt( event->pos(), true ) ); } return true; } //_____________________________________________________________ bool Client::dragLeaveEvent( QDragLeaveEvent* ) { if( _itemData.animationType() & AnimationSameTarget ) { if( _dragStartTimer.isActive() ) _dragStartTimer.stop(); _itemData.animate( AnimationLeave|AnimationSameTarget, _sourceItem ); } else if( _itemData.isAnimated() ) { _itemData.animate( AnimationLeave ); } return true; } //_____________________________________________________________ bool Client::dragMoveEvent( QDragMoveEvent* event ) { // check format if( !event->mimeData()->hasFormat( tabDragMimeType() ) ) return false; // animate if( event->source() != widget() ) { _itemData.animate( AnimationMove, tabIndexAt( event->pos(), true ) ); } else if( _itemData.count() > 1 ) { if( _dragStartTimer.isActive() ) _dragStartTimer.stop(); _itemData.animate( AnimationMove|AnimationSameTarget, tabIndexAt( event->pos(), true ) ); } return false; } //_____________________________________________________________ bool Client::dropEvent( QDropEvent* event ) { const QPoint point = event->pos(); _itemData.animate( AnimationNone ); const QMimeData *groupData = event->mimeData(); if( !groupData->hasFormat( tabDragMimeType() ) ) return false; _itemData.setDirty( true ); if( widget() != event->source() ) setForceActive( true ); const long source = QString::fromUtf8( groupData->data( tabDragMimeType() ) ).toLong(); const int clickedIndex( tabIndexAt( point, true ) ); if( clickedIndex < 0 ) tab_A_behind_B( source, tabId(_itemData.count()-1) ); else tab_A_before_B( source, tabId(clickedIndex) ); // update title if( widget() == event->source() ) updateTitleRect(); _titleAnimationData->reset(); return true; } //_____________________________________________________________ void Client::timerEvent( QTimerEvent* event ) { if( event->timerId() != _dragStartTimer.timerId() ) - { return KCommonDecorationUnstable::timerEvent( event ); } + { return KCommonDecoration::timerEvent( event ); } _dragStartTimer.stop(); // do nothing if there is only one tab if( _itemData.count() > 1 ) { _itemData.animate( AnimationMove|AnimationSameTarget, _sourceItem ); _itemData.animate( AnimationLeave|AnimationSameTarget, _sourceItem ); } } //_____________________________________________________________ bool Client::closeItem( const Button* button ) { for( int i=0; i < _itemData.count(); i++ ) { if( button == _itemData[i]._closeButton.data() ) { _itemData.setDirty( true ); closeTab( tabId(i) ); return true; } } return false; } //________________________________________________________________ QPixmap Client::itemDragPixmap( int index, QRect geometry, bool drawShadow ) { const bool itemValid( index >= 0 && index < tabCount() ); QPixmap pixmap( geometry.size() ); pixmap.fill( Qt::transparent ); QPainter painter( &pixmap ); painter.setRenderHints(QPainter::SmoothPixmapTransform|QPainter::Antialiasing); painter.translate( -geometry.topLeft() ); // draw shadows if( drawShadow ) { // shadow const int shadowSize( shadowCache().shadowSize() ); TileSet *tileSet( shadowCache().tileSet( ShadowCache::Key() ) ); tileSet->render( geometry, &painter, TileSet::Ring); geometry.adjust( shadowSize, shadowSize, -shadowSize, -shadowSize ); renderCorners( &painter, geometry, widget()->palette() ); } // mask painter.setClipRegion( helper().roundedMask( geometry ), Qt::IntersectClip ); // render window background renderWindowBackground( &painter, geometry, widget(), widget()->palette() ); // darken background if item is inactive const bool itemActive = (tabCount() <= 1) || !( itemValid && tabId(index) != currentTabId() ); if( !itemActive ) { QLinearGradient lg( geometry.topLeft(), geometry.bottomLeft() ); lg.setColorAt( 0, helper().alphaColor( Qt::black, 0.05 ) ); lg.setColorAt( 1, helper().alphaColor( Qt::black, 0.10 ) ); painter.setBrush( lg ); painter.setPen( Qt::NoPen ); painter.drawRect( geometry ); } // render title text painter.setFont( options()->font(isActive(), false) ); QRect textRect( geometry.adjusted( 0, layoutMetric( LM_TitleEdgeTop )-1, 0, -1 ) ); if( itemValid ) { textRect.adjust( layoutMetric( LM_TitleBorderLeft ), 0, -layoutMetric(LM_TitleBorderRight), 0 ); } const QString caption( itemValid ? this->caption(index) : this->caption() ); renderTitleText( &painter, textRect, caption, titlebarTextColor( widget()->palette(), isActive() && itemActive ), titlebarContrastColor( widget()->palette() ) ); // adjust geometry for floatFrame when compositing is on. if( drawShadow ) { geometry.adjust(-1, -1, 1, 1 ); } // floating frame helper().drawFloatFrame( &painter, geometry, widget()->palette().window().color(), !drawShadow, false, KDecoration::options()->color(ColorTitleBar) ); painter.end(); return pixmap; } //_________________________________________________________________ void Client::createSizeGrip( void ) { assert( !hasSizeGrip() ); if( ( isResizable() && windowId() != 0 ) || isPreview() ) { _sizeGrip = new SizeGrip( this ); sizeGrip().setVisible( !( isMaximized() || isShade() ) ); } } //_________________________________________________________________ void Client::deleteSizeGrip( void ) { assert( hasSizeGrip() ); _sizeGrip->deleteLater(); _sizeGrip = 0; } //_________________________________________________________________ void Client::removeShadowHint( void ) { // do nothing if no window id if( !windowId() ) return; // create atom if( !_shadowAtom ) { _shadowAtom = XInternAtom( QX11Info::display(), "_KDE_NET_WM_SHADOW", False); } XDeleteProperty(QX11Info::display(), windowId(), _shadowAtom); } } diff --git a/kwin/clients/oxygen/oxygenclient.h b/kwin/clients/oxygen/oxygenclient.h index 2a74539de7..11bdd1152d 100644 --- a/kwin/clients/oxygen/oxygenclient.h +++ b/kwin/clients/oxygen/oxygenclient.h @@ -1,545 +1,545 @@ #ifndef oxygenclient_h #define oxygenclient_h ////////////////////////////////////////////////////////////////////////////// // oxygenclient.h // ------------------- // // Copyright (c) 2009 Hugo Pereira Da Costa // Copyright (c) 2003, 2004 David Johnson // Copyright (c) 2006, 2007 Riccardo Iaconelli // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to // deal in the Software without restriction, including without limitation the // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or // sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. ////////////////////////////////////////////////////////////////////////////// #include "oxygenanimation.h" #include "oxygenclientgroupitemdata.h" #include "oxygenconfiguration.h" #include "oxygendecohelper.h" #include "oxygenfactory.h" #include "oxygenshadowcache.h" #include "oxygentitleanimationdata.h" #include #include #include #include #include namespace Oxygen { class SizeGrip; - class Client : public KCommonDecorationUnstable + class Client : public KCommonDecoration { Q_OBJECT //! declare glow intensity property Q_PROPERTY( qreal glowIntensity READ glowIntensityUnbiased WRITE setGlowIntensity ) public: //! constructor Client(KDecorationBridge *b, Factory *f); //! destructor virtual ~Client(); //! decoration name virtual QString visibleName() const; //! buttons virtual KCommonDecorationButton *createButton(::ButtonType type); //!@name flags //@{ //! true if decoration has iquired behavior virtual bool decorationBehaviour(DecorationBehaviour behaviour) const; //! true if window is maximized virtual bool isMaximized( void ) const { return maximizeMode()==MaximizeFull && !configuration()->drawBorderOnMaximizedWindows(); } //! true if animations are used bool animationsEnabled( void ) const { return _configuration->animationsEnabled(); } //! true if glow is animated bool glowIsAnimated( void ) const { return _glowAnimation->isRunning(); } //! true when decoration is forced active bool isForcedActive( void ) const { return _forceActive && tabCount() > 1; } //! true when separator is to be drawn bool drawSeparator( void ) const { if( _configuration->drawTitleOutline() ) return false; switch( _configuration->separatorMode() ) { case Configuration::SeparatorAlways: return true; case Configuration::SeparatorActive: return ( glowIsAnimated() || isActive() ); default: case Configuration::SeparatorNever: return false; } } //! true if titlebar is hidden bool hideTitleBar( void ) const { return _configuration->hideTitleBar() && !isShade() && tabCount() == 1; } //@} //! window shape virtual void updateWindowShape(); //! initialization virtual void init(); // reset virtual void reset( unsigned long changed ); //! return associated configuration Factory::ConfigurationPtr configuration( void ) const { return _configuration; } //!@name glow animation //@{ void setGlowIntensity( qreal value ) { if( _glowIntensity == value ) return; _glowIntensity = value; widget()->update(); } //! unbiased glow intensity qreal glowIntensityUnbiased( void ) const { return _glowIntensity; } //! glow bias static qreal glowBias( void ) { return 0.2; } //! true (biased) intensity /*! this is needed to have glow go from either 0.2->1 or 0.8->0 depending on the animation direction */ qreal glowIntensity( void ) const { return _glowAnimation->direction() == Animation::Forward ? _glowIntensity : _glowIntensity-glowBias(); } //@} //! helper class DecoHelper& helper( void ) const { return _factory->helper(); } //! helper class ShadowCache& shadowCache( void ) const { return _factory->shadowCache(); } //!@name metrics and color definitions //@{ //! dimensions virtual int layoutMetric(LayoutMetric lm, bool respectWindowState = true, const KCommonDecorationButton * = 0) const; //! get title rect for untabbed window virtual QRect defaultTitleRect( bool active = true ) const; //! get title bounding rect virtual QRect titleBoundingRect( const QFont& font, const QString& caption ) const { return titleBoundingRect( font, titleRect(), caption ); } //! get title bounding rect virtual QRect titleBoundingRect( const QFont&, QRect, const QString& ) const; //! palette background QPalette backgroundPalette( const QWidget*, QPalette ) const; //! background QColor backgroundColor( const QWidget* widget, QPalette palette ) const { return backgroundColor( widget, palette, isActive() || isForcedActive() ); } //! background QColor backgroundColor( const QWidget*, QPalette, bool ) const; //@} //! default buttons located on the left virtual QString defaultButtonsLeft() const; //! default buttons located on the right virtual QString defaultButtonsRight() const; //! title alignment inline Qt::Alignment titleAlignment( void ) const; //! button size inline int buttonSize( void ) const; //! frame border inline int frameBorder( void ) const; //!@name status change methods (overloaded from KCommonDecorationUnstable) //@{ //! triggered when window activity is changed virtual void activeChange(); //! triggered when maximize state changed virtual void maximizeChange(); //! triggered when window shade is changed virtual void shadeChange(); //! triggered when window shade is changed virtual void captionChange(); //@} //! event filter virtual bool eventFilter( QObject*, QEvent* ); //! resize event virtual void resizeEvent( QResizeEvent* ); //! paint background to painter void paintBackground( QPainter& ) const; public Q_SLOTS: //! triggers widget update in titleRect only /*! one needs to add the title top margin to avoid some clipping glitches */ void updateTitleRect( void ) { widget()->update( titleRect().adjusted( 0, -layoutMetric( LM_TitleEdgeTop ), 0, 1 ) ); } //! return region for a given defines. This allows to implement extended borders QRegion region( KDecorationDefines::Region ); protected: //! return shadow cache key associated to this client ShadowCache::Key key( void ) const { ShadowCache::Key key; key.active = ( isActive() || isForcedActive() ) && shadowCache().isEnabled( QPalette::Active ); key.isShade = isShade(); key.hasBorder = ( _configuration->frameBorder() > Configuration::BorderNone ); return key; } //! true when decoration is forced active void setForceActive( bool value ) { _forceActive = value; } //!@name event filters //@{ //! paint virtual void paintEvent( QPaintEvent* ); //! render full decoration to provided painter virtual void paint( QPainter& ); //! mouse press event virtual bool mousePressEvent( QMouseEvent* ); //! mouse release event virtual bool mouseReleaseEvent( QMouseEvent* ); //! mouse move event virtual bool mouseMoveEvent( QMouseEvent* ); //! drag enter event virtual bool dragEnterEvent( QDragEnterEvent* ); //! drag move event virtual bool dragMoveEvent( QDragMoveEvent* ); //! drag leave event virtual bool dragLeaveEvent( QDragLeaveEvent* ); //! drop event virtual bool dropEvent( QDropEvent* ); //! timer event virtual void timerEvent( QTimerEvent* ); //@} //!@name rendering methods (called in paintEvent) //@{ //! window background virtual void renderWindowBackground( QPainter*, const QRect&, const QWidget*, const QPalette& ) const; //! window border // this draws a "blue" border around active window virtual void renderWindowBorder( QPainter*, const QRect&, const QWidget*, const QPalette& ) const; //! separator virtual void renderSeparator( QPainter*, const QRect&, const QWidget*, const QColor& ) const; //! title outline virtual void renderTitleOutline( QPainter*, const QRect&, const QPalette& ) const; //! title text /*! second color, if valid, is for contrast pixel */ virtual void renderTitleText( QPainter*, const QRect&, const QColor&, const QColor& = QColor() ) const; //! title text /*! second color, if valid, is for contrast pixel */ virtual void renderTitleText( QPainter*, const QRect&, const QString&, const QColor&, const QColor& = QColor(), bool elide = true ) const; //! title text virtual QPixmap renderTitleText( const QRect&, const QString&, const QColor&, bool elide = true ) const; //! GroupItem virtual void renderItem( QPainter*, int, const QPalette& ); //! tabbing target rect virtual void renderTargetRect( QPainter*, const QPalette& ); //! render corners virtual void renderCorners( QPainter*, const QRect&, const QPalette& ) const; //! render float frame virtual void renderFloatFrame( QPainter*, const QRect&, const QPalette& ) const; //! render dots virtual void renderDots( QPainter*, const QRect&, const QColor& ) const; //@} //! close tab matching give button virtual bool closeItem( const Button* ); //! index of item matching point int tabIndexAt( const QPoint& position, bool between = false ) const { return _itemData.itemAt( position , between ); } //! return pixmap corresponding to a given tab, for dragging QPixmap itemDragPixmap( int, QRect, bool = false ); //! return true when activity change are animated bool shadowAnimationsEnabled( void ) const { return ( animationsEnabled() && _configuration->shadowAnimationsEnabled() && !isPreview() ); } //! return true when activity change are animated bool titleAnimationsEnabled( void ) const { return animationsEnabled() && _configuration->titleAnimationsEnabled() && !_configuration->drawTitleOutline() && !hideTitleBar() && !isPreview(); } //! true if some title outline is rendered bool hasTitleOutline( void ) const { return tabCount() >= 2 || _itemData.isAnimated() || ( (isActive()||glowIsAnimated()) && _configuration->drawTitleOutline() ); } //! calculate mask QRegion calcMask( void ) const; //! text color QColor titlebarTextColor(const QPalette&) const; //! text color QColor titlebarTextColor(const QPalette& palette, bool active) const { return active ? palette.color(QPalette::Active, QPalette::WindowText): helper().inactiveTitleBarTextColor( palette ); } //! text color QColor titlebarContrastColor(const QPalette& palette ) const { return titlebarContrastColor( palette.color( widget()->window()->backgroundRole() ) ); } //! text color QColor titlebarContrastColor(const QColor& color ) const { return helper().calcLightColor( color ); } //!@name size grip //@{ //! create size grip void createSizeGrip( void ); //! delete size grip void deleteSizeGrip( void ); // size grip bool hasSizeGrip( void ) const { return (bool)_sizeGrip; } //! size grip SizeGrip& sizeGrip( void ) const { return *_sizeGrip; } //@} //! remove shadow hint void removeShadowHint( void ); protected Q_SLOTS: //! set target item to -1 void clearTargetItem( void ); //! clear force active flag void clearForceActive( void ) { if( isActive() ) setForceActive( false ); } //! title bounding rects /*! calculate and return title bounding rects in case of tabbed window */ void updateItemBoundingRects( bool alsoUpdate = true ); //! bound one rect to another void boundRectTo( QRect&, const QRect& ) const; private: //! factory Factory* _factory; //! backing store pixmap (when compositing is not active) QPixmap _pixmap; //! size grip widget SizeGrip* _sizeGrip; //! configuration Factory::ConfigurationPtr _configuration; //! glow animation Animation* _glowAnimation; //! title animation data TitleAnimationData* _titleAnimationData; //! glow intensity qreal _glowIntensity; //! true when initialized bool _initialized; //! true when decoration is forced active bool _forceActive; //! mouse button Qt::MouseButton _mouseButton; //! tab bounding rects ClientGroupItemDataList _itemData; //! index of tab being dragged if any, -1 otherwise int _sourceItem; //! drag start point QPoint _dragPoint; //! drag start timer. /*! it is needed to activate animations when this was not done via either dragMoveEvent or dragLeaveEvent */ QBasicTimer _dragStartTimer; //! shadow atom Atom _shadowAtom; }; } // namespace Oxygen //____________________________________________________ Qt::Alignment Oxygen::Client::titleAlignment( void ) const { switch( _configuration->titleAlignment() ) { case Configuration::AlignLeft: return Qt::AlignLeft; case Configuration::AlignRight: return Qt::AlignRight; default: case Configuration::AlignCenter: case Configuration::AlignCenterFullWidth: return Qt::AlignCenter; } } //____________________________________________________ int Oxygen::Client::buttonSize( void ) const { switch( _configuration->buttonSize() ) { case Configuration::ButtonSmall: return 18; default: case Configuration::ButtonDefault: return 20; case Configuration::ButtonLarge: return 24; case Configuration::ButtonVeryLarge: return 32; case Configuration::ButtonHuge: return 48; } } //____________________________________________________ int Oxygen::Client::frameBorder( void ) const { switch( _configuration->frameBorder() ) { case Configuration::BorderNone: return 0; case Configuration::BorderNoSide: return 1; default: case Configuration::BorderTiny: return 2; case Configuration::BorderDefault: return 4; case Configuration::BorderLarge: return 8; case Configuration::BorderVeryLarge: return 12; case Configuration::BorderHuge: return 18; case Configuration::BorderVeryHuge: return 27; case Configuration::BorderOversized: return 40; } } #endif diff --git a/kwin/events.cpp b/kwin/events.cpp index cdb577ad50..55be799f89 100644 --- a/kwin/events.cpp +++ b/kwin/events.cpp @@ -1,1559 +1,1557 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 1999, 2000 Matthias Ettrich Copyright (C) 2003 Lubos Lunak This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ /* This file contains things relevant to handling incoming events. */ #include "client.h" #include "cursor.h" #include "decorations.h" #include "focuschain.h" #include "netinfo.h" #include "workspace.h" #include "atoms.h" #ifdef KWIN_BUILD_TABBOX #include "tabbox.h" #endif #include "group.h" #include "overlaywindow.h" #include "rules.h" #include "unmanaged.h" #include "useractions.h" #include "effects.h" #ifdef KWIN_BUILD_SCREENEDGES #include "screenedge.h" #endif #include "screens.h" #include "xcbutils.h" #include #include #include #include #include #include #include "composite.h" #include "killwindow.h" namespace KWin { extern int currentRefreshRate(); // **************************************** // Workspace // **************************************** static xcb_window_t findEventWindow(xcb_generic_event_t *event) { const uint8_t eventType = event->response_type & ~0x80; switch(eventType) { case XCB_KEY_PRESS: case XCB_KEY_RELEASE: return reinterpret_cast(event)->event; case XCB_BUTTON_PRESS: case XCB_BUTTON_RELEASE: return reinterpret_cast(event)->event; case XCB_MOTION_NOTIFY: return reinterpret_cast(event)->event; case XCB_ENTER_NOTIFY: case XCB_LEAVE_NOTIFY: return reinterpret_cast(event)->event; case XCB_FOCUS_IN: case XCB_FOCUS_OUT: return reinterpret_cast(event)->event; case XCB_EXPOSE: return reinterpret_cast(event)->window; case XCB_GRAPHICS_EXPOSURE: return reinterpret_cast(event)->drawable; case XCB_NO_EXPOSURE: return reinterpret_cast(event)->drawable; case XCB_VISIBILITY_NOTIFY: return reinterpret_cast(event)->window; case XCB_CREATE_NOTIFY: return reinterpret_cast(event)->window; case XCB_DESTROY_NOTIFY: return reinterpret_cast(event)->window; case XCB_UNMAP_NOTIFY: return reinterpret_cast(event)->window; case XCB_MAP_NOTIFY: return reinterpret_cast(event)->window; case XCB_MAP_REQUEST: return reinterpret_cast(event)->window; case XCB_REPARENT_NOTIFY: return reinterpret_cast(event)->window; case XCB_CONFIGURE_NOTIFY: return reinterpret_cast(event)->window; case XCB_CONFIGURE_REQUEST: return reinterpret_cast(event)->window; case XCB_GRAVITY_NOTIFY: return reinterpret_cast(event)->window; case XCB_RESIZE_REQUEST: return reinterpret_cast(event)->window; case XCB_CIRCULATE_NOTIFY: case XCB_CIRCULATE_REQUEST: return reinterpret_cast(event)->window; case XCB_PROPERTY_NOTIFY: return reinterpret_cast(event)->window; case XCB_COLORMAP_NOTIFY: return reinterpret_cast(event)->window; case XCB_CLIENT_MESSAGE: return reinterpret_cast(event)->window; default: // extension handling if (eventType == Xcb::Extensions::self()->shapeNotifyEvent()) { return reinterpret_cast(event)->affected_window; } if (eventType == Xcb::Extensions::self()->damageNotifyEvent()) { return reinterpret_cast(event)->drawable; } return XCB_WINDOW_NONE; } } /*! Handles workspace specific XCB event */ bool Workspace::workspaceEvent(xcb_generic_event_t *e) { const uint8_t eventType = e->response_type & ~0x80; if (effects && static_cast< EffectsHandlerImpl* >(effects)->hasKeyboardGrab() && (eventType == XCB_KEY_PRESS || eventType == XCB_KEY_RELEASE)) return false; // let Qt process it, it'll be intercepted again in eventFilter() if (!m_windowKiller.isNull() && m_windowKiller->isActive() && m_windowKiller->isResponsibleForEvent(eventType)) { m_windowKiller->processEvent(e); // filter out the event return true; } if (eventType == XCB_PROPERTY_NOTIFY || eventType == XCB_CLIENT_MESSAGE) { unsigned long dirty[ NETRootInfo::PROPERTIES_SIZE ]; rootInfo()->event(e, dirty, NETRootInfo::PROPERTIES_SIZE); if (dirty[ NETRootInfo::PROTOCOLS ] & NET::DesktopNames) VirtualDesktopManager::self()->save(); if (dirty[ NETRootInfo::PROTOCOLS2 ] & NET::WM2DesktopLayout) VirtualDesktopManager::self()->updateLayout(); } // events that should be handled before Clients can get them switch (eventType) { case XCB_BUTTON_PRESS: case XCB_BUTTON_RELEASE: { was_user_interaction = true; auto *mouseEvent = reinterpret_cast(e); #ifdef KWIN_BUILD_TABBOX if (TabBox::TabBox::self()->isGrabbed()) { return TabBox::TabBox::self()->handleMouseEvent(mouseEvent); } #endif if (effects && static_cast(effects)->checkInputWindowEvent(mouseEvent)) { return true; } break; } case XCB_MOTION_NOTIFY: { m_mouseMotionTimer->cancel(); auto *mouseEvent = reinterpret_cast(e); const QPoint rootPos(mouseEvent->root_x, mouseEvent->root_y); #ifdef KWIN_BUILD_TABBOX if (TabBox::TabBox::self()->isGrabbed()) { #ifdef KWIN_BUILD_SCREENEDGES ScreenEdges::self()->check(rootPos, QDateTime::fromMSecsSinceEpoch(xTime()), true); #endif return TabBox::TabBox::self()->handleMouseEvent(mouseEvent); } #endif if (effects && static_cast(effects)->checkInputWindowEvent(mouseEvent)) { return true; } #ifdef KWIN_BUILD_SCREENEDGES if (QWidget::mouseGrabber()) { ScreenEdges::self()->check(rootPos, QDateTime::fromMSecsSinceEpoch(xTime()), true); } #endif break; } case XCB_KEY_PRESS: { was_user_interaction = true; int keyQt; KKeyServer::xcbKeyPressEventToQt(reinterpret_cast(e), &keyQt); // kDebug(125) << "Workspace::keyPress( " << keyQt << " )"; if (movingClient) { movingClient->keyPressEvent(keyQt); return true; } #ifdef KWIN_BUILD_TABBOX if (TabBox::TabBox::self()->isGrabbed()) { TabBox::TabBox::self()->keyPress(keyQt); return true; } #endif break; } case XCB_KEY_RELEASE: was_user_interaction = true; #ifdef KWIN_BUILD_TABBOX if (TabBox::TabBox::self()->isGrabbed()) { TabBox::TabBox::self()->keyRelease(reinterpret_cast(e)); return true; } #endif break; case XCB_CONFIGURE_NOTIFY: if (reinterpret_cast(e)->event == rootWindow()) x_stacking_dirty = true; break; }; const xcb_window_t eventWindow = findEventWindow(e); if (eventWindow != XCB_WINDOW_NONE) { if (Client* c = findClient(WindowMatchPredicate(eventWindow))) { if (c->windowEvent(e)) return true; } else if (Client* c = findClient(WrapperIdMatchPredicate(eventWindow))) { if (c->windowEvent(e)) return true; } else if (Client* c = findClient(FrameIdMatchPredicate(eventWindow))) { if (c->windowEvent(e)) return true; } else if (Client *c = findClient(InputIdMatchPredicate(eventWindow))) { if (c->windowEvent(e)) return true; } else if (Unmanaged* c = findUnmanaged(WindowMatchPredicate(eventWindow))) { if (c->windowEvent(e)) return true; } else { // We want to pass root window property events to effects if (eventType == XCB_PROPERTY_NOTIFY) { auto *event = reinterpret_cast(e); if (event->window == rootWindow()) { emit propertyNotify(event->atom); } } } } if (movingClient) { if (eventType == XCB_BUTTON_PRESS || eventType == XCB_BUTTON_RELEASE) { if (movingClient->moveResizeGrabWindow() == reinterpret_cast(e)->event && movingClient->windowEvent(e)) { return true; } } else if (eventType == XCB_MOTION_NOTIFY) { if (movingClient->moveResizeGrabWindow() == reinterpret_cast(e)->event && movingClient->windowEvent(e)) { return true; } } } switch (eventType) { case XCB_CREATE_NOTIFY: { const auto *event = reinterpret_cast(e); if (event->parent == rootWindow() && !QWidget::find(event->window) && !event->override_redirect) { // see comments for allowClientActivation() const xcb_timestamp_t t = xTime(); xcb_change_property(connection(), XCB_PROP_MODE_REPLACE, event->window, atoms->kde_net_wm_user_creation_time, XCB_ATOM_CARDINAL, 32, 1, &t); } break; } case XCB_UNMAP_NOTIFY: { const auto *event = reinterpret_cast(e); return (event->event != event->window); // hide wm typical event from Qt } case XCB_REPARENT_NOTIFY: { //do not confuse Qt with these events. After all, _we_ are the //window manager who does the reparenting. return true; } case XCB_DESTROY_NOTIFY: { return false; } case XCB_MAP_REQUEST: { updateXTime(); const auto *event = reinterpret_cast(e); if (Client* c = findClient(WindowMatchPredicate(event->window))) { // e->xmaprequest.window is different from e->xany.window // TODO this shouldn't be necessary now c->windowEvent(e); FocusChain::self()->update(c, FocusChain::Update); } else if ( true /*|| e->xmaprequest.parent != root */ ) { // NOTICE don't check for the parent being the root window, this breaks when some app unmaps // a window, changes something and immediately maps it back, without giving KWin // a chance to reparent it back to root // since KWin can get MapRequest only for root window children and // children of WindowWrapper (=clients), the check is AFAIK useless anyway // NOTICE: The save-set support in Client::mapRequestEvent() actually requires that // this code doesn't check the parent to be root. if (!createClient(event->window, false)) { xcb_map_window(connection(), event->window); const uint32_t values[] = { XCB_STACK_MODE_ABOVE }; xcb_configure_window(connection(), event->window, XCB_CONFIG_WINDOW_STACK_MODE, values); } } return true; } case XCB_MAP_NOTIFY: { const auto *event = reinterpret_cast(e); if (event->override_redirect) { Unmanaged* c = findUnmanaged(WindowMatchPredicate(event->window)); if (c == NULL) c = createUnmanaged(event->window); if (c) return c->windowEvent(e); } return (event->event != event->window); // hide wm typical event from Qt } case XCB_ENTER_NOTIFY: { if (QWhatsThis::inWhatsThisMode()) { QWidget* w = QWidget::find(reinterpret_cast(e)->event); if (w) QWhatsThis::leaveWhatsThisMode(); } #ifdef KWIN_BUILD_SCREENEDGES if (ScreenEdges::self()->isEntered(reinterpret_cast(e))) return true; #endif break; } case XCB_LEAVE_NOTIFY: { if (!QWhatsThis::inWhatsThisMode()) break; // TODO is this cliente ever found, given that client events are searched above? const auto *event = reinterpret_cast(e); Client* c = findClient(FrameIdMatchPredicate(event->event)); if (c && event->detail != XCB_NOTIFY_DETAIL_INFERIOR) QWhatsThis::leaveWhatsThisMode(); break; } case XCB_CONFIGURE_REQUEST: { const auto *event = reinterpret_cast(e); if (event->parent == rootWindow()) { // TODO: this should be ported to xcb XWindowChanges wc; wc.border_width = event->border_width; wc.x = event->x; wc.y = event->y; wc.width = event->width; wc.height = event->height; wc.sibling = XCB_WINDOW_NONE; wc.stack_mode = XCB_STACK_MODE_ABOVE; unsigned int value_mask = event->value_mask & (XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT | XCB_CONFIG_WINDOW_BORDER_WIDTH); XConfigureWindow(display(), event->window, value_mask, &wc); return true; } break; } case XCB_FOCUS_IN: { const auto *event = reinterpret_cast(e); if (event->event == rootWindow() && (event->detail == XCB_NOTIFY_DETAIL_NONE || event->detail == XCB_NOTIFY_DETAIL_POINTER_ROOT)) { Xcb::CurrentInput currentInput; updateXTime(); // focusToNull() uses xTime(), which is old now (FocusIn has no timestamp) if (!currentInput.isNull() && (currentInput->focus == XCB_WINDOW_NONE || currentInput->focus == XCB_NOTIFY_DETAIL_POINTER_ROOT)) { //kWarning( 1212 ) << "X focus set to None/PointerRoot, reseting focus" ; Client *c = mostRecentlyActivatedClient(); if (c != NULL) requestFocus(c, true); else if (activateNextClient(NULL)) ; // ok, activated else focusToNull(); } } } // fall through case XCB_FOCUS_OUT: return true; // always eat these, they would tell Qt that KWin is the active app case XCB_CLIENT_MESSAGE: #ifdef KWIN_BUILD_SCREENEDGES if (ScreenEdges::self()->isEntered(reinterpret_cast(e))) return true; #endif break; case XCB_EXPOSE: { const auto *event = reinterpret_cast(e); if (compositing() && (event->window == rootWindow() // root window needs repainting || (m_compositor->overlayWindow() != XCB_WINDOW_NONE && event->window == m_compositor->overlayWindow()))) { // overlay needs repainting m_compositor->addRepaint(event->x, event->y, event->width, event->height); } break; } case XCB_VISIBILITY_NOTIFY: { const auto *event = reinterpret_cast(e); if (compositing() && m_compositor->overlayWindow() != XCB_WINDOW_NONE && event->window == m_compositor->overlayWindow()) { bool was_visible = m_compositor->isOverlayWindowVisible(); m_compositor->setOverlayWindowVisibility((event->state != XCB_VISIBILITY_FULLY_OBSCURED)); if (!was_visible && m_compositor->isOverlayWindowVisible()) { // hack for #154825 m_compositor->addRepaintFull(); QTimer::singleShot(2000, m_compositor, SLOT(addRepaintFull())); } m_compositor->scheduleRepaint(); } break; } default: if (eventType == Xcb::Extensions::self()->randrNotifyEvent() && Xcb::Extensions::self()->isRandrAvailable()) { auto *event = reinterpret_cast(e); xcb_screen_t *screen = defaultScreen(); if (event->rotation & (XCB_RANDR_ROTATION_ROTATE_90 | XCB_RANDR_ROTATION_ROTATE_270)) { screen->width_in_pixels = event->height; screen->height_in_pixels = event->width; screen->width_in_millimeters = event->mheight; screen->height_in_millimeters = event->mwidth; } else { screen->width_in_pixels = event->width; screen->height_in_pixels = event->height; screen->width_in_millimeters = event->mwidth; screen->height_in_millimeters = event->mheight; } if (compositing()) { // desktopResized() should take care of when the size or // shape of the desktop has changed, but we also want to // catch refresh rate changes if (m_compositor->xrrRefreshRate() != currentRefreshRate()) m_compositor->setCompositeResetTimer(0); } } else if (eventType == Xcb::Extensions::self()->syncAlarmNotifyEvent() && Xcb::Extensions::self()->isSyncAvailable()) { for (Client *c : clients) c->syncEvent(reinterpret_cast< xcb_sync_alarm_notify_event_t* >(e)); for (Client *c : desktops) c->syncEvent(reinterpret_cast< xcb_sync_alarm_notify_event_t* >(e)); } else if (eventType == Xcb::Extensions::self()->fixesCursorNotifyEvent() && Xcb::Extensions::self()->isFixesAvailable()) { Cursor::self()->notifyCursorChanged(reinterpret_cast(e)->cursor_serial); } break; } return false; } // Used only to filter events that need to be processed by Qt first // (e.g. keyboard input to be composed), otherwise events are // handle by the XEvent filter above bool Workspace::workspaceEvent(QEvent* e) { if ((e->type() == QEvent::KeyPress || e->type() == QEvent::KeyRelease || e->type() == QEvent::ShortcutOverride) && effects && static_cast< EffectsHandlerImpl* >(effects)->hasKeyboardGrab()) { static_cast< EffectsHandlerImpl* >(effects)->grabbedKeyboardEvent(static_cast< QKeyEvent* >(e)); return true; } return false; } // **************************************** // Client // **************************************** /*! General handler for XEvents concerning the client window */ bool Client::windowEvent(xcb_generic_event_t *e) { if (findEventWindow(e) == window()) { // avoid doing stuff on frame or wrapper unsigned long dirty[ 2 ]; double old_opacity = opacity(); info->event(e, dirty, 2); // pass through the NET stuff if ((dirty[ NETWinInfo::PROTOCOLS ] & NET::WMName) != 0) fetchName(); if ((dirty[ NETWinInfo::PROTOCOLS ] & NET::WMIconName) != 0) fetchIconicName(); if ((dirty[ NETWinInfo::PROTOCOLS ] & NET::WMStrut) != 0 || (dirty[ NETWinInfo::PROTOCOLS2 ] & NET::WM2ExtendedStrut) != 0) { workspace()->updateClientArea(); } if ((dirty[ NETWinInfo::PROTOCOLS ] & NET::WMIcon) != 0) getIcons(); // Note there's a difference between userTime() and info->userTime() // info->userTime() is the value of the property, userTime() also includes // updates of the time done by KWin (ButtonPress on windowrapper etc.). if ((dirty[ NETWinInfo::PROTOCOLS2 ] & NET::WM2UserTime) != 0) { workspace()->setWasUserInteraction(); updateUserTime(info->userTime()); } if ((dirty[ NETWinInfo::PROTOCOLS2 ] & NET::WM2StartupId) != 0) startupIdChanged(); if (dirty[ NETWinInfo::PROTOCOLS2 ] & NET::WM2Opacity) { if (compositing()) { addRepaintFull(); emit opacityChanged(this, old_opacity); } else { // forward to the frame if there's possibly another compositing manager running NETWinInfo2 i(display(), frameId(), rootWindow(), 0); i.setOpacity(info->opacity()); } } if (dirty[ NETWinInfo::PROTOCOLS2 ] & NET::WM2FrameOverlap) { // ### Inform the decoration } } const uint8_t eventType = e->response_type & ~0x80; switch(eventType) { case XCB_UNMAP_NOTIFY: unmapNotifyEvent(reinterpret_cast(e)); break; case XCB_DESTROY_NOTIFY: destroyNotifyEvent(reinterpret_cast(e)); break; case XCB_MAP_REQUEST: // this one may pass the event to workspace return mapRequestEvent(reinterpret_cast(e)); case XCB_CONFIGURE_REQUEST: configureRequestEvent(reinterpret_cast(e)); break; case XCB_PROPERTY_NOTIFY: propertyNotifyEvent(reinterpret_cast(e)); break; case XCB_KEY_PRESS: updateUserTime(); workspace()->setWasUserInteraction(); break; case XCB_BUTTON_PRESS: { const auto *event = reinterpret_cast(e); updateUserTime(); workspace()->setWasUserInteraction(); buttonPressEvent(event->event, event->detail, event->state, event->event_x, event->event_y, event->root_x, event->root_y); break; } case XCB_KEY_RELEASE: // don't update user time on releases // e.g. if the user presses Alt+F2, the Alt release // would appear as user input to the currently active window break; case XCB_BUTTON_RELEASE: { const auto *event = reinterpret_cast(e); // don't update user time on releases // e.g. if the user presses Alt+F2, the Alt release // would appear as user input to the currently active window buttonReleaseEvent(event->event, event->detail, event->state, event->event_x, event->event_y, event->root_x, event->root_y); break; } case XCB_MOTION_NOTIFY: { const auto *event = reinterpret_cast(e); motionNotifyEvent(event->event, event->state, event->event_x, event->event_y, event->root_x, event->root_y); workspace()->updateFocusMousePosition(QPoint(event->root_x, event->root_y)); break; } case XCB_ENTER_NOTIFY: { auto *event = reinterpret_cast(e); enterNotifyEvent(event); // MotionNotify is guaranteed to be generated only if the mouse // move start and ends in the window; for cases when it only // starts or only ends there, Enter/LeaveNotify are generated. // Fake a MotionEvent in such cases to make handle of mouse // events simpler (Qt does that too). motionNotifyEvent(event->event, event->state, event->event_x, event->event_y, event->root_x, event->root_y); workspace()->updateFocusMousePosition(QPoint(event->root_x, event->root_y)); break; } case XCB_LEAVE_NOTIFY: { auto *event = reinterpret_cast(e); motionNotifyEvent(event->event, event->state, event->event_x, event->event_y, event->root_x, event->root_y); leaveNotifyEvent(event); // not here, it'd break following enter notify handling // workspace()->updateFocusMousePosition( QPoint( e->xcrossing.x_root, e->xcrossing.y_root )); break; } case XCB_FOCUS_IN: focusInEvent(reinterpret_cast(e)); break; case XCB_FOCUS_OUT: focusOutEvent(reinterpret_cast(e)); break; case XCB_REPARENT_NOTIFY: break; case XCB_CLIENT_MESSAGE: clientMessageEvent(reinterpret_cast(e)); break; default: if (eventType == Xcb::Extensions::self()->shapeNotifyEvent() && reinterpret_cast(e)->affected_window == window()) { detectShape(window()); // workaround for #19644 updateShape(); } if (eventType == Xcb::Extensions::self()->damageNotifyEvent() && reinterpret_cast(e)->drawable == frameId()) damageNotifyEvent(); break; } return true; // eat all events } /*! Handles map requests of the client window */ bool Client::mapRequestEvent(xcb_map_request_event_t *e) { if (e->window != window()) { // Special support for the save-set feature, which is a bit broken. // If there's a window from one client embedded in another one, // e.g. using XEMBED, and the embedder suddenly loses its X connection, // save-set will reparent the embedded window to its closest ancestor // that will remains. Unfortunately, with reparenting window managers, // this is not the root window, but the frame (or in KWin's case, // it's the wrapper for the client window). In this case, // the wrapper will get ReparentNotify for a window it won't know, // which will be ignored, and then it gets MapRequest, as save-set // always maps. Returning true here means that Workspace::workspaceEvent() // will handle this MapRequest and manage this window (i.e. act as if // it was reparented to root window). if (e->parent == wrapperId()) return false; return true; // no messing with frame etc. } // also copied in clientMessage() if (isMinimized()) unminimize(); if (isShade()) setShade(ShadeNone); if (!isOnCurrentDesktop()) { if (workspace()->allowClientActivation(this)) workspace()->activateClient(this); else demandAttention(); } return true; } /*! Handles unmap notify events of the client window */ void Client::unmapNotifyEvent(xcb_unmap_notify_event_t *e) { if (e->window != window()) return; if (e->event != wrapperId()) { // most probably event from root window when initially reparenting bool ignore = true; if (e->event == rootWindow() && (e->response_type & 0x80)) ignore = false; // XWithdrawWindow() if (ignore) return; } // check whether this is result of an XReparentWindow - client then won't be parented by wrapper // in this case do not release the client (causes reparent to root, removal from saveSet and what not) // but just destroy the client Xcb::Tree tree(m_client); xcb_window_t daddy = tree.parent(); if (daddy == m_wrapper) { releaseWindow(); // unmapped from a regular client state } else { destroyClient(); // the client was moved to some other parent } } void Client::destroyNotifyEvent(xcb_destroy_notify_event_t *e) { if (e->window != window()) return; destroyClient(); } /*! Handles client messages for the client window */ void Client::clientMessageEvent(xcb_client_message_event_t *e) { if (e->window != window()) return; // ignore frame/wrapper // WM_STATE if (e->type == atoms->kde_wm_change_state) { bool avoid_animation = (e->data.data32[ 1 ]); if (e->data.data32[ 0 ] == XCB_ICCCM_WM_STATE_ICONIC) minimize(); else if (e->data.data32[ 0 ] == XCB_ICCCM_WM_STATE_NORMAL) { // copied from mapRequest() if (isMinimized()) unminimize(avoid_animation); if (isShade()) setShade(ShadeNone); if (!isOnCurrentDesktop()) { if (workspace()->allowClientActivation(this)) workspace()->activateClient(this); else demandAttention(); } } } else if (e->type == atoms->wm_change_state) { if (e->data.data32[0] == XCB_ICCCM_WM_STATE_ICONIC) minimize(); return; } } /*! Handles configure requests of the client window */ void Client::configureRequestEvent(xcb_configure_request_event_t *e) { if (e->window != window()) return; // ignore frame/wrapper if (isResize() || isMove()) return; // we have better things to do right now if (fullscreen_mode == FullScreenNormal) { // refuse resizing of fullscreen windows // but allow resizing fullscreen hacks in order to let them cancel fullscreen mode sendSyntheticConfigureNotify(); return; } if (isSplash()) { // no manipulations with splashscreens either sendSyntheticConfigureNotify(); return; } if (e->value_mask & XCB_CONFIG_WINDOW_BORDER_WIDTH) { // first, get rid of a window border m_client.setBorderWidth(0); } if (e->value_mask & (XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_HEIGHT | XCB_CONFIG_WINDOW_WIDTH)) configureRequest(e->value_mask, e->x, e->y, e->width, e->height, 0, false); if (e->value_mask & XCB_CONFIG_WINDOW_STACK_MODE) restackWindow(e->sibling, e->stack_mode, NET::FromApplication, userTime(), false); // Sending a synthetic configure notify always is fine, even in cases where // the ICCCM doesn't require this - it can be though of as 'the WM decided to move // the window later'. The client should not cause that many configure request, // so this should not have any significant impact. With user moving/resizing // the it should be optimized though (see also Client::setGeometry()/plainResize()/move()). sendSyntheticConfigureNotify(); // SELI TODO accept configure requests for isDesktop windows (because kdesktop // may get XRANDR resize event before kwin), but check it's still at the bottom? } /*! Handles property changes of the client window */ void Client::propertyNotifyEvent(xcb_property_notify_event_t *e) { Toplevel::propertyNotifyEvent(e); if (e->window != window()) return; // ignore frame/wrapper switch(e->atom) { case XCB_ATOM_WM_NORMAL_HINTS: getWmNormalHints(); break; case XCB_ATOM_WM_NAME: fetchName(); break; case XCB_ATOM_WM_ICON_NAME: fetchIconicName(); break; case XCB_ATOM_WM_TRANSIENT_FOR: readTransient(); break; case XCB_ATOM_WM_HINTS: getWMHints(); getIcons(); // because KWin::icon() uses WMHints as fallback break; default: if (e->atom == atoms->wm_protocols) getWindowProtocols(); else if (e->atom == atoms->motif_wm_hints) getMotifHints(); else if (e->atom == atoms->net_wm_sync_request_counter) getSyncCounter(); else if (e->atom == atoms->activities) checkActivities(); else if (e->atom == atoms->kde_net_wm_block_compositing) updateCompositeBlocking(true); else if (e->atom == atoms->kde_first_in_window_list) updateFirstInTabBox(); break; } } void Client::enterNotifyEvent(xcb_enter_notify_event_t *e) { if (e->event != frameId()) return; // care only about entering the whole frame #define MOUSE_DRIVEN_FOCUS (!options->focusPolicyIsReasonable() || \ (options->focusPolicy() == Options::FocusFollowsMouse && options->isNextFocusPrefersMouse())) if (e->mode == XCB_NOTIFY_MODE_NORMAL || (e->mode == XCB_NOTIFY_MODE_UNGRAB && MOUSE_DRIVEN_FOCUS)) { if (options->isShadeHover()) { cancelShadeHoverTimer(); if (isShade()) { shadeHoverTimer = new QTimer(this); connect(shadeHoverTimer, SIGNAL(timeout()), this, SLOT(shadeHover())); shadeHoverTimer->setSingleShot(true); shadeHoverTimer->start(options->shadeHoverInterval()); } } #undef MOUSE_DRIVEN_FOCUS if (options->focusPolicy() == Options::ClickToFocus || workspace()->userActionsMenu()->isShown()) return; QPoint currentPos(e->root_x, e->root_y); if (options->isAutoRaise() && !isDesktop() && !isDock() && workspace()->focusChangeEnabled() && currentPos != workspace()->focusMousePosition() && workspace()->topClientOnDesktop(VirtualDesktopManager::self()->current(), options->isSeparateScreenFocus() ? screen() : -1) != this) { delete autoRaiseTimer; autoRaiseTimer = new QTimer(this); connect(autoRaiseTimer, SIGNAL(timeout()), this, SLOT(autoRaise())); autoRaiseTimer->setSingleShot(true); autoRaiseTimer->start(options->autoRaiseInterval()); } if (isDesktop() || isDock()) return; // for FocusFollowsMouse, change focus only if the mouse has actually been moved, not if the focus // change came because of window changes (e.g. closing a window) - #92290 if (options->focusPolicy() != Options::FocusFollowsMouse || currentPos != workspace()->focusMousePosition()) { workspace()->requestDelayFocus(this); } return; } } void Client::leaveNotifyEvent(xcb_leave_notify_event_t *e) { if (e->event != frameId()) return; // care only about leaving the whole frame if (e->mode == XCB_NOTIFY_MODE_NORMAL) { if (!buttonDown) { mode = PositionCenter; updateCursor(); } bool lostMouse = !rect().contains(QPoint(e->event_x, e->event_y)); // 'lostMouse' wouldn't work with e.g. B2 or Keramik, which have non-rectangular decorations // (i.e. the LeaveNotify event comes before leaving the rect and no LeaveNotify event // comes after leaving the rect) - so lets check if the pointer is really outside the window // TODO this still sucks if a window appears above this one - it should lose the mouse // if this window is another client, but not if it's a popup ... maybe after KDE3.1 :( // (repeat after me 'AARGHL!') if (!lostMouse && e->detail != XCB_NOTIFY_DETAIL_INFERIOR) { // TODO: port to XCB int d1, d2, d3, d4; unsigned int d5; Window w, child; if (XQueryPointer(display(), frameId(), &w, &child, &d1, &d2, &d3, &d4, &d5) == False || child == XCB_WINDOW_NONE) lostMouse = true; // really lost the mouse } if (lostMouse) { cancelAutoRaise(); workspace()->cancelDelayFocus(); cancelShadeHoverTimer(); if (shade_mode == ShadeHover && !moveResizeMode && !buttonDown) { shadeHoverTimer = new QTimer(this); connect(shadeHoverTimer, SIGNAL(timeout()), this, SLOT(shadeUnhover())); shadeHoverTimer->setSingleShot(true); shadeHoverTimer->start(options->shadeHoverInterval()); } } if (options->focusPolicy() == Options::FocusStrictlyUnderMouse && isActive() && lostMouse) { workspace()->requestDelayFocus(0); } return; } } #define XCapL KKeyServer::modXLock() #define XNumL KKeyServer::modXNumLock() #define XScrL KKeyServer::modXScrollLock() void Client::grabButton(int modifier) { unsigned int mods[ 8 ] = { 0, XCapL, XNumL, XNumL | XCapL, XScrL, XScrL | XCapL, XScrL | XNumL, XScrL | XNumL | XCapL }; for (int i = 0; i < 8; ++i) m_wrapper.grabButton(XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, modifier | mods[ i ]); } void Client::ungrabButton(int modifier) { unsigned int mods[ 8 ] = { 0, XCapL, XNumL, XNumL | XCapL, XScrL, XScrL | XCapL, XScrL | XNumL, XScrL | XNumL | XCapL }; for (int i = 0; i < 8; ++i) m_wrapper.ungrabButton(modifier | mods[ i ]); } #undef XCapL #undef XNumL #undef XScrL /* Releases the passive grab for some modifier combinations when a window becomes active. This helps broken X programs that missinterpret LeaveNotify events in grab mode to work properly (Motif, AWT, Tk, ...) */ void Client::updateMouseGrab() { if (workspace()->globalShortcutsDisabled()) { m_wrapper.ungrabButton(); // keep grab for the simple click without modifiers if needed (see below) bool not_obscured = workspace()->topClientOnDesktop(VirtualDesktopManager::self()->current(), -1, true, false) == this; if (!(!options->isClickRaise() || not_obscured)) grabButton(None); return; } if (isActive() && !workspace()->forcedGlobalMouseGrab()) { // see Workspace::establishTabBoxGrab() // first grab all modifier combinations m_wrapper.grabButton(XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC); // remove the grab for no modifiers only if the window // is unobscured or if the user doesn't want click raise // (it is unobscured if it the topmost in the unconstrained stacking order, i.e. it is // the most recently raised window) bool not_obscured = workspace()->topClientOnDesktop(VirtualDesktopManager::self()->current(), -1, true, false) == this; if (!options->isClickRaise() || not_obscured) ungrabButton(None); else grabButton(None); ungrabButton(ShiftMask); ungrabButton(ControlMask); ungrabButton(ControlMask | ShiftMask); } else { m_wrapper.ungrabButton(); // simply grab all modifier combinations m_wrapper.grabButton(XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC); } } // Qt propagates mouse events up the widget hierachy, which means events // for the decoration window cannot be (easily) intercepted as X11 events bool Client::eventFilter(QObject* o, QEvent* e) { if (decoration == NULL || o != decoration->widget()) return false; if (e->type() == QEvent::MouseButtonPress) { QMouseEvent* ev = static_cast< QMouseEvent* >(e); return buttonPressEvent(decorationId(), qtToX11Button(ev->button()), qtToX11State(ev->buttons(), ev->modifiers()), ev->x(), ev->y(), ev->globalX(), ev->globalY()); } if (e->type() == QEvent::MouseButtonRelease) { QMouseEvent* ev = static_cast< QMouseEvent* >(e); return buttonReleaseEvent(decorationId(), qtToX11Button(ev->button()), qtToX11State(ev->buttons(), ev->modifiers()), ev->x(), ev->y(), ev->globalX(), ev->globalY()); } if (e->type() == QEvent::MouseMove) { // FRAME i fake z enter/leave? QMouseEvent* ev = static_cast< QMouseEvent* >(e); return motionNotifyEvent(decorationId(), qtToX11State(ev->buttons(), ev->modifiers()), ev->x(), ev->y(), ev->globalX(), ev->globalY()); } if (e->type() == QEvent::Wheel) { QWheelEvent* ev = static_cast< QWheelEvent* >(e); bool r = buttonPressEvent(decorationId(), ev->delta() > 0 ? Button4 : Button5, qtToX11State(ev->buttons(), ev->modifiers()), ev->x(), ev->y(), ev->globalX(), ev->globalY()); r = r || buttonReleaseEvent(decorationId(), ev->delta() > 0 ? Button4 : Button5, qtToX11State(ev->buttons(), ev->modifiers()), ev->x(), ev->y(), ev->globalX(), ev->globalY()); return r; } if (e->type() == QEvent::Resize) { QResizeEvent* ev = static_cast< QResizeEvent* >(e); // Filter out resize events that inform about size different than frame size. // This will ensure that decoration->width() etc. and decoration->widget()->width() will be in sync. // These events only seem to be delayed events from initial resizing before show() was called // on the decoration widget. if (ev->size() != (size() + QSize(padding_left + padding_right, padding_top + padding_bottom))) return true; // HACK: Avoid decoration redraw delays. On resize Qt sets WA_WStateConfigPending // which delays all painting until a matching ConfigureNotify event comes. // But this process itself is the window manager, so it's not needed // to wait for that event, the geometry is known. // Note that if Qt in the future changes how this flag is handled and what it // triggers then this may potentionally break things. See mainly QETWidget::translateConfigEvent(). decoration->widget()->setAttribute(Qt::WA_WState_ConfigPending, false); decoration->widget()->update(); return false; } return false; } static bool modKeyDown(int state) { const uint keyModX = (options->keyCmdAllModKey() == Qt::Key_Meta) ? KKeyServer::modXMeta() : KKeyServer::modXAlt(); return keyModX && (state & KKeyServer::accelModMaskX()) == keyModX; } // return value matters only when filtering events before decoration gets them bool Client::buttonPressEvent(xcb_window_t w, int button, int state, int x, int y, int x_root, int y_root) { if (buttonDown) { if (w == wrapperId()) xcb_allow_events(connection(), XCB_ALLOW_SYNC_POINTER, XCB_TIME_CURRENT_TIME); //xTime()); return true; } if (w == wrapperId() || w == frameId() || w == decorationId() || w == inputId()) { // FRAME neco s tohohle by se melo zpracovat, nez to dostane dekorace updateUserTime(); workspace()->setWasUserInteraction(); const bool bModKeyHeld = modKeyDown(state); if (isSplash() && button == Button1 && !bModKeyHeld) { // hide splashwindow if the user clicks on it hideClient(true); if (w == wrapperId()) xcb_allow_events(connection(), XCB_ALLOW_SYNC_POINTER, XCB_TIME_CURRENT_TIME); //xTime()); return true; } Options::MouseCommand com = Options::MouseNothing; bool was_action = false; bool perform_handled = false; if (bModKeyHeld) { was_action = true; switch(button) { case Button1: com = options->commandAll1(); break; case Button2: com = options->commandAll2(); break; case Button3: com = options->commandAll3(); break; case Button4: case Button5: com = options->operationWindowMouseWheel(button == Button4 ? 120 : -120); break; } } else { // inactive inner window if (!isActive() && w == wrapperId() && button < 6) { was_action = true; perform_handled = true; switch(button) { case Button1: com = options->commandWindow1(); break; case Button2: com = options->commandWindow2(); break; case Button3: com = options->commandWindow3(); break; case Button4: case Button5: com = options->commandWindowWheel(); break; } } // active inner window if (isActive() && w == wrapperId() && options->isClickRaise() && button < 4) { // exclude wheel com = Options::MouseActivateRaiseAndPassClick; was_action = true; perform_handled = true; } } if (was_action) { bool replay = performMouseCommand(com, QPoint(x_root, y_root), perform_handled); if (isSpecialWindow()) replay = true; if (w == wrapperId()) // these can come only from a grab xcb_allow_events(connection(), replay ? XCB_ALLOW_REPLAY_POINTER : XCB_ALLOW_SYNC_POINTER, XCB_TIME_CURRENT_TIME); //xTime()); return true; } } if (w == wrapperId()) { // these can come only from a grab xcb_allow_events(connection(), XCB_ALLOW_REPLAY_POINTER, XCB_TIME_CURRENT_TIME); //xTime()); return true; } if (w == inputId()) { x = x_root - geometry().x() + padding_left; y = y_root - geometry().y() + padding_top; // New API processes core events FIRST and only passes unused ones to the decoration return processDecorationButtonPress(button, state, x, y, x_root, y_root, true); } if (w == decorationId()) { - if (dynamic_cast(decoration)) - // New API processes core events FIRST and only passes unused ones to the decoration - return processDecorationButtonPress(button, state, x, y, x_root, y_root, true); - return false; + // New API processes core events FIRST and only passes unused ones to the decoration + return processDecorationButtonPress(button, state, x, y, x_root, y_root, true); } if (w == frameId()) processDecorationButtonPress(button, state, x, y, x_root, y_root); return true; } // this function processes button press events only after decoration decides not to handle them, // unlike buttonPressEvent(), which (when the window is decoration) filters events before decoration gets them bool Client::processDecorationButtonPress(int button, int /*state*/, int x, int y, int x_root, int y_root, bool ignoreMenu) { Options::MouseCommand com = Options::MouseNothing; bool active = isActive(); if (!wantsInput()) // we cannot be active, use it anyway active = true; if (button == Button1) com = active ? options->commandActiveTitlebar1() : options->commandInactiveTitlebar1(); else if (button == Button2) com = active ? options->commandActiveTitlebar2() : options->commandInactiveTitlebar2(); else if (button == Button3) com = active ? options->commandActiveTitlebar3() : options->commandInactiveTitlebar3(); if (button == Button1 && com != Options::MouseOperationsMenu // actions where it's not possible to get the matching && com != Options::MouseMinimize // mouse release event && com != Options::MouseDragTab) { mode = mousePosition(QPoint(x, y)); buttonDown = true; moveOffset = QPoint(x - padding_left, y - padding_top); invertedMoveOffset = rect().bottomRight() - moveOffset; unrestrictedMoveResize = false; startDelayedMoveResize(); updateCursor(); } // In the new API the decoration may process the menu action to display an inactive tab's menu. // If the event is unhandled then the core will create one for the active window in the group. if (!ignoreMenu || com != Options::MouseOperationsMenu) performMouseCommand(com, QPoint(x_root, y_root)); return !( // Return events that should be passed to the decoration in the new API com == Options::MouseRaise || com == Options::MouseOperationsMenu || com == Options::MouseActivateAndRaise || com == Options::MouseActivate || com == Options::MouseActivateRaiseAndPassClick || com == Options::MouseActivateAndPassClick || com == Options::MouseDragTab || com == Options::MouseNothing); } // called from decoration void Client::processMousePressEvent(QMouseEvent* e) { if (e->type() != QEvent::MouseButtonPress) { qWarning() << "processMousePressEvent()" ; return; } int button; switch(e->button()) { case Qt::LeftButton: button = Button1; break; case Qt::MidButton: button = Button2; break; case Qt::RightButton: button = Button3; break; default: return; } processDecorationButtonPress(button, e->buttons(), e->x(), e->y(), e->globalX(), e->globalY()); } // return value matters only when filtering events before decoration gets them bool Client::buttonReleaseEvent(xcb_window_t w, int /*button*/, int state, int x, int y, int x_root, int y_root) { if (w == decorationId() && !buttonDown) return false; if (w == wrapperId()) { xcb_allow_events(connection(), XCB_ALLOW_SYNC_POINTER, XCB_TIME_CURRENT_TIME); //xTime()); return true; } if (w != frameId() && w != decorationId() && w != inputId() && w != moveResizeGrabWindow()) return true; x = this->x(); // translate from grab window to local coords y = this->y(); if ((state & (Button1Mask & Button2Mask & Button3Mask)) == 0) { buttonDown = false; stopDelayedMoveResize(); if (moveResizeMode) { finishMoveResize(false); // mouse position is still relative to old Client position, adjust it QPoint mousepos(x_root - x + padding_left, y_root - y + padding_top); mode = mousePosition(mousepos); } else if (decorationPlugin()->supportsTabbing()) return false; updateCursor(); } return true; } // Checks if the mouse cursor is near the edge of the screen and if so activates quick tiling or maximization void Client::checkQuickTilingMaximizationZones(int xroot, int yroot) { QuickTileMode mode = QuickTileNone; for (int i=0; icount(); ++i) { if (!screens()->geometry(i).contains(QPoint(xroot, yroot))) continue; QRect area = workspace()->clientArea(MaximizeArea, QPoint(xroot, yroot), desktop()); if (options->electricBorderTiling()) { if (xroot <= area.x() + 20) mode |= QuickTileLeft; else if (xroot >= area.x() + area.width() - 20) mode |= QuickTileRight; } if (mode != QuickTileNone) { if (yroot <= area.y() + area.height() * options->electricBorderCornerRatio()) mode |= QuickTileTop; else if (yroot >= area.y() + area.height() - area.height() * options->electricBorderCornerRatio()) mode |= QuickTileBottom; } else if (options->electricBorderMaximize() && yroot <= area.y() + 5 && isMaximizable()) mode = QuickTileMaximize; break; // no point in checking other screens to contain this... "point"... } setElectricBorderMode(mode); setElectricBorderMaximizing(mode != QuickTileNone); } // return value matters only when filtering events before decoration gets them bool Client::motionNotifyEvent(xcb_window_t w, int state, int x, int y, int x_root, int y_root) { if (w != frameId() && w != decorationId() && w != inputId() && w != moveResizeGrabWindow()) return true; // care only about the whole frame if (!buttonDown) { QPoint mousePos(x, y); if (w == frameId()) mousePos += QPoint(padding_left, padding_top); if (w == inputId()) { int x = x_root - geometry().x() + padding_left; int y = y_root - geometry().y() + padding_top; mousePos = QPoint(x, y); } Position newmode = modKeyDown(state) ? PositionCenter : mousePosition(mousePos); if (newmode != mode) { mode = newmode; updateCursor(); } return false; } if (w == moveResizeGrabWindow()) { x = this->x(); // translate from grab window to local coords y = this->y(); } // mouse motion event compression: the event queue might have multiple motion events // in that case we are only interested in the last event to not cause too much overhead // by useless move/resize operations. // The compression is done using a singleshot QTimer of 0 msec to just move the processing // to the end of the event queue. In case there is another motion event in the queue it will // be processed before the timer fires and the processing of the newer motion event cancels // the running timer. Eventually this code path will be reached again and the timer is // started again workspace()->scheduleMouseMotionCompression([this, x, y, x_root, y_root]() { const QRect oldGeo = geometry(); handleMoveResize(x, y, x_root, y_root); if (!isFullScreen() && isMove()) { if (quick_tile_mode != QuickTileNone && oldGeo != geometry()) { GeometryUpdatesBlocker blocker(this); setQuickTileMode(QuickTileNone); moveOffset = QPoint(double(moveOffset.x()) / double(oldGeo.width()) * double(geom_restore.width()), double(moveOffset.y()) / double(oldGeo.height()) * double(geom_restore.height())); moveResizeGeom = geom_restore; handleMoveResize(x, y, x_root, y_root); // fix position } else if (quick_tile_mode == QuickTileNone && isResizable()) { checkQuickTilingMaximizationZones(x_root, y_root); } } }); return true; } void Client::focusInEvent(xcb_focus_in_event_t *e) { if (e->event != window()) return; // only window gets focus if (e->mode == XCB_NOTIFY_MODE_UNGRAB) return; // we don't care if (e->detail == XCB_NOTIFY_DETAIL_POINTER) return; // we don't care if (!isShown(false) || !isOnCurrentDesktop()) // we unmapped it, but it got focus meanwhile -> return; // activateNextClient() already transferred focus elsewhere workspace()->forEachClient([](Client *client) { client->cancelFocusOutTimer(); }); // check if this client is in should_get_focus list or if activation is allowed bool activate = workspace()->allowClientActivation(this, -1U, true); workspace()->gotFocusIn(this); // remove from should_get_focus list if (activate) setActive(true); else { workspace()->restoreFocus(); demandAttention(); } } void Client::focusOutEvent(xcb_focus_out_event_t *e) { if (e->event != window()) return; // only window gets focus if (e->mode == XCB_NOTIFY_MODE_GRAB) return; // we don't care if (isShade()) return; // here neither if (e->detail != XCB_NOTIFY_DETAIL_NONLINEAR && e->detail != XCB_NOTIFY_DETAIL_NONLINEAR_VIRTUAL) // SELI check all this return; // hack for motif apps like netscape if (QApplication::activePopupWidget()) return; // When a client loses focus, FocusOut events are usually immediatelly // followed by FocusIn events for another client that gains the focus // (unless the focus goes to another screen, or to the nofocus widget). // Without this check, the former focused client would have to be // deactivated, and after that, the new one would be activated, with // a short time when there would be no active client. This can cause // flicker sometimes, e.g. when a fullscreen is shown, and focus is transferred // from it to its transient, the fullscreen would be kept in the Active layer // at the beginning and at the end, but not in the middle, when the active // client would be temporarily none (see Client::belongToLayer() ). // Therefore the setActive(false) call is moved to the end of the current // event queue. If there is a matching FocusIn event in the current queue // this will be processed before the setActive(false) call and the activation // of the Client which gained FocusIn will automatically deactivate the // previously active client. if (!m_focusOutTimer) { m_focusOutTimer = new QTimer(this); m_focusOutTimer->setSingleShot(true); m_focusOutTimer->setInterval(0); connect(m_focusOutTimer, &QTimer::timeout, [this]() { setActive(false); }); } m_focusOutTimer->start(); } // performs _NET_WM_MOVERESIZE void Client::NETMoveResize(int x_root, int y_root, NET::Direction direction) { if (direction == NET::Move) performMouseCommand(Options::MouseMove, QPoint(x_root, y_root)); else if (moveResizeMode && direction == NET::MoveResizeCancel) { finishMoveResize(true); buttonDown = false; updateCursor(); } else if (direction >= NET::TopLeft && direction <= NET::Left) { static const Position convert[] = { PositionTopLeft, PositionTop, PositionTopRight, PositionRight, PositionBottomRight, PositionBottom, PositionBottomLeft, PositionLeft }; if (!isResizable() || isShade()) return; if (moveResizeMode) finishMoveResize(false); buttonDown = true; moveOffset = QPoint(x_root - x(), y_root - y()); // map from global invertedMoveOffset = rect().bottomRight() - moveOffset; unrestrictedMoveResize = false; mode = convert[ direction ]; if (!startMoveResize()) buttonDown = false; updateCursor(); } else if (direction == NET::KeyboardMove) { // ignore mouse coordinates given in the message, mouse position is used by the moving algorithm Cursor::setPos(geometry().center()); performMouseCommand(Options::MouseUnrestrictedMove, geometry().center()); } else if (direction == NET::KeyboardSize) { // ignore mouse coordinates given in the message, mouse position is used by the resizing algorithm Cursor::setPos(geometry().bottomRight()); performMouseCommand(Options::MouseUnrestrictedResize, geometry().bottomRight()); } } void Client::keyPressEvent(uint key_code) { updateUserTime(); if (!isMove() && !isResize()) return; bool is_control = key_code & Qt::CTRL; bool is_alt = key_code & Qt::ALT; key_code = key_code & ~Qt::KeyboardModifierMask; int delta = is_control ? 1 : is_alt ? 32 : 8; QPoint pos = cursorPos(); switch(key_code) { case Qt::Key_Left: pos.rx() -= delta; break; case Qt::Key_Right: pos.rx() += delta; break; case Qt::Key_Up: pos.ry() -= delta; break; case Qt::Key_Down: pos.ry() += delta; break; case Qt::Key_Space: case Qt::Key_Return: case Qt::Key_Enter: finishMoveResize(false); buttonDown = false; updateCursor(); break; case Qt::Key_Escape: finishMoveResize(true); buttonDown = false; updateCursor(); break; default: return; } Cursor::setPos(pos); } void Client::syncEvent(xcb_sync_alarm_notify_event_t* e) { if (e->alarm == syncRequest.alarm && e->counter_value.hi == syncRequest.value.hi && e->counter_value.lo == syncRequest.value.lo) { setReadyForPainting(); syncRequest.isPending = false; if (syncRequest.failsafeTimeout) syncRequest.failsafeTimeout->stop(); if (isResize()) { if (syncRequest.timeout) syncRequest.timeout->stop(); performMoveResize(); } else // setReadyForPainting does as well, but there's a small chance for resize syncs after the resize ended addRepaintFull(); } } // **************************************** // Unmanaged // **************************************** bool Unmanaged::windowEvent(xcb_generic_event_t *e) { double old_opacity = opacity(); unsigned long dirty[ 2 ]; info->event(e, dirty, 2); // pass through the NET stuff if (dirty[ NETWinInfo::PROTOCOLS2 ] & NET::WM2Opacity) { if (compositing()) { addRepaintFull(); emit opacityChanged(this, old_opacity); } } const uint8_t eventType = e->response_type & ~0x80; switch (eventType) { case XCB_UNMAP_NOTIFY: workspace()->updateFocusMousePosition(Cursor::pos()); release(); break; case XCB_CONFIGURE_NOTIFY: configureNotifyEvent(reinterpret_cast(e)); break; case XCB_PROPERTY_NOTIFY: propertyNotifyEvent(reinterpret_cast(e)); break; default: { if (eventType == Xcb::Extensions::self()->shapeNotifyEvent()) { detectShape(window()); addRepaintFull(); addWorkspaceRepaint(geometry()); // in case shape change removes part of this window emit geometryShapeChanged(this, geometry()); } if (eventType == Xcb::Extensions::self()->damageNotifyEvent()) damageNotifyEvent(); break; } } return false; // don't eat events, even our own unmanaged widgets are tracked } void Unmanaged::configureNotifyEvent(xcb_configure_notify_event_t *e) { if (effects) static_cast(effects)->checkInputWindowStacking(); // keep them on top QRect newgeom(e->x, e->y, e->width, e->height); if (newgeom != geom) { addWorkspaceRepaint(visibleRect()); // damage old area QRect old = geom; geom = newgeom; addRepaintFull(); if (old.size() != geom.size()) discardWindowPixmap(); emit geometryShapeChanged(this, old); } } // **************************************** // Toplevel // **************************************** void Toplevel::propertyNotifyEvent(xcb_property_notify_event_t *e) { if (e->window != window()) return; // ignore frame/wrapper switch(e->atom) { default: if (e->atom == atoms->wm_client_leader) getWmClientLeader(); else if (e->atom == atoms->wm_window_role) getWindowRole(); else if (e->atom == atoms->kde_net_wm_shadow) getShadow(); else if (e->atom == atoms->net_wm_opaque_region) getWmOpaqueRegion(); break; } emit propertyNotify(this, e->atom); } } // namespace diff --git a/kwin/libkdecorations/kcommondecoration.cpp b/kwin/libkdecorations/kcommondecoration.cpp index 7857bc3ca3..20fc9a7cf4 100644 --- a/kwin/libkdecorations/kcommondecoration.cpp +++ b/kwin/libkdecorations/kcommondecoration.cpp @@ -1,1447 +1,1431 @@ /* This file is part of the KDE project. Copyright (C) 2005 Sandro Giessl Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "kcommondecoration.h" #include "kcommondecoration_p.h" #include #include #include #include #include #include #include "kdecorationfactory.h" #include #include "kcommondecoration.moc" /** @addtogroup kdecoration */ /** @{ */ KCommonDecoration::KCommonDecoration(KDecorationBridge* bridge, KDecorationFactory* factory) : m_previewWidget(0), btnHideMinWidth(200), btnHideLastWidth(0), closing(false), wrapper(new KCommonDecorationWrapper(this, bridge, factory)) { // sizeof(...) is calculated at compile time memset(m_button, 0, sizeof(KCommonDecorationButton *) * NumButtons); connect(wrapper, SIGNAL(keepAboveChanged(bool)), this, SIGNAL(keepAboveChanged(bool))); connect(wrapper, SIGNAL(keepBelowChanged(bool)), this, SIGNAL(keepBelowChanged(bool))); } KCommonDecoration::~KCommonDecoration() { for (int n = 0; n < NumButtons; n++) { if (m_button[n]) delete m_button[n]; } delete m_previewWidget; // delete wrapper; - do not do this, this object is actually owned and deleted by the wrapper } QString KCommonDecoration::defaultButtonsLeft() const { return KDecorationOptions::defaultTitleButtonsLeft(); } QString KCommonDecoration::defaultButtonsRight() const { return KDecorationOptions::defaultTitleButtonsRight(); } bool KCommonDecoration::decorationBehaviour(DecorationBehaviour behaviour) const { switch(behaviour) { case DB_MenuClose: return false; case DB_WindowMask: return false; case DB_ButtonHide: return true; } return false; } int KCommonDecoration::layoutMetric(LayoutMetric lm, bool respectWindowState, const KCommonDecorationButton *button) const { switch(lm) { case LM_BorderLeft: case LM_BorderRight: case LM_BorderBottom: case LM_TitleEdgeTop: case LM_TitleEdgeBottom: case LM_TitleEdgeLeft: case LM_TitleEdgeRight: case LM_TitleBorderLeft: case LM_TitleBorderRight: return 5; case LM_ButtonWidth: case LM_ButtonHeight: case LM_TitleHeight: return 20; case LM_ButtonSpacing: return 5; case LM_ButtonMarginTop: return 0; case LM_ExplicitButtonSpacer: return layoutMetric(LM_ButtonWidth, respectWindowState, button) / 2; // half button width by default default: return 0; } } void KCommonDecoration::init() { createMainWidget(); // for flicker-free redraws widget()->setAttribute(Qt::WA_NoSystemBackground); widget()->installEventFilter(this); resetLayout(); connect(this, SIGNAL(keepAboveChanged(bool)), SLOT(keepAboveChange(bool))); connect(this, SIGNAL(keepBelowChanged(bool)), SLOT(keepBelowChange(bool))); updateCaption(); } void KCommonDecoration::reset(unsigned long changed) { if (changed & SettingButtons) { resetLayout(); widget()->update(); } } QRegion KCommonDecoration::cornerShape(WindowCorner) { return QRegion(); } void KCommonDecoration::updateCaption() { // This should be reimplemented in decorations for better efficiency widget()->update(); } void KCommonDecoration::borders(int& left, int& right, int& top, int& bottom) const { left = layoutMetric(LM_BorderLeft); right = layoutMetric(LM_BorderRight); bottom = layoutMetric(LM_BorderBottom); top = layoutMetric(LM_TitleHeight) + layoutMetric(LM_TitleEdgeTop) + layoutMetric(LM_TitleEdgeBottom); updateLayout(); // TODO!! don't call every time we are in ::borders } void KCommonDecoration::updateLayout() const { const int paddingLeft = layoutMetric(LM_OuterPaddingLeft); const int paddingTop = layoutMetric(LM_OuterPaddingTop); const int paddingRight = layoutMetric(LM_OuterPaddingRight); const int paddingBottom = layoutMetric(LM_OuterPaddingBottom); QRect r = widget()->rect().adjusted(paddingLeft, paddingTop, -paddingRight, -paddingBottom); int r_x, r_y, r_x2, r_y2; r.getCoords(&r_x, &r_y, &r_x2, &r_y2); // layout preview widget if (m_previewWidget) { const int borderLeft = layoutMetric(LM_BorderLeft); const int borderRight = layoutMetric(LM_BorderRight); const int borderBottom = layoutMetric(LM_BorderBottom); const int titleHeight = layoutMetric(LM_TitleHeight); const int titleEdgeTop = layoutMetric(LM_TitleEdgeTop); const int titleEdgeBottom = layoutMetric(LM_TitleEdgeBottom); int left = r_x + borderLeft; int top = r_y + titleEdgeTop + titleHeight + titleEdgeBottom; int width = r_x2 - borderRight - left + 1; int height = r_y2 - borderBottom - top + 1; m_previewWidget->setGeometry(left, top, width, height); moveWidget(left, top, m_previewWidget); resizeWidget(width, height, m_previewWidget); } // resize buttons... for (int n = 0; n < NumButtons; n++) { if (m_button[n]) { QSize newSize = QSize(layoutMetric(LM_ButtonWidth, true, m_button[n]), layoutMetric(LM_ButtonHeight, true, m_button[n])); if (newSize != m_button[n]->size()) m_button[n]->setSize(newSize); } } // layout buttons int y = r_y + layoutMetric(LM_TitleEdgeTop) + layoutMetric(LM_ButtonMarginTop); if (m_buttonsLeft.count() > 0) { const int buttonSpacing = layoutMetric(LM_ButtonSpacing); int x = r_x + layoutMetric(LM_TitleEdgeLeft); for (ButtonContainer::const_iterator it = m_buttonsLeft.begin(); it != m_buttonsLeft.end(); ++it) { bool elementLayouted = false; if (*it) { if (!(*it)->isHidden()) { moveWidget(x, y, *it); x += layoutMetric(LM_ButtonWidth, true, qobject_cast(*it)); elementLayouted = true; } } else { x += layoutMetric(LM_ExplicitButtonSpacer); elementLayouted = true; } if (elementLayouted && it != m_buttonsLeft.end()) x += buttonSpacing; } } if (m_buttonsRight.count() > 0) { const int titleEdgeRightLeft = r_x2 - layoutMetric(LM_TitleEdgeRight) + 1; const int buttonSpacing = layoutMetric(LM_ButtonSpacing); int x = titleEdgeRightLeft - buttonContainerWidth(m_buttonsRight); for (ButtonContainer::const_iterator it = m_buttonsRight.begin(); it != m_buttonsRight.end(); ++it) { bool elementLayouted = false; if (*it) { if (!(*it)->isHidden()) { moveWidget(x, y, *it); x += layoutMetric(LM_ButtonWidth, true, qobject_cast(*it));; elementLayouted = true; } } else { x += layoutMetric(LM_ExplicitButtonSpacer); elementLayouted = true; } if (elementLayouted && it != m_buttonsRight.end()) x += buttonSpacing; } } } void KCommonDecoration::updateButtons() const { for (int n = 0; n < NumButtons; n++) if (m_button[n]) m_button[n]->update(); } void KCommonDecoration::resetButtons() const { for (int n = 0; n < NumButtons; n++) if (m_button[n]) m_button[n]->reset(KCommonDecorationButton::ManualReset); } void KCommonDecoration::resetLayout() { for (int n = 0; n < NumButtons; n++) { if (m_button[n]) { delete m_button[n]; m_button[n] = 0; } } m_buttonsLeft.clear(); m_buttonsRight.clear(); delete m_previewWidget; m_previewWidget = 0; // shown instead of the window contents in decoration previews if (isPreview()) { m_previewWidget = new QLabel(i18n("
%1
", visibleName()), widget()); m_previewWidget->setAutoFillBackground(true); m_previewWidget->show(); // fix double deletion, see buttonDestroyed() connect(m_previewWidget, SIGNAL(destroyed(QObject*)), this, SLOT(objDestroyed(QObject*))); } addButtons(m_buttonsLeft, options()->customButtonPositions() ? options()->titleButtonsLeft() : defaultButtonsLeft(), true); addButtons(m_buttonsRight, options()->customButtonPositions() ? options()->titleButtonsRight() : defaultButtonsRight(), false); updateLayout(); const int minTitleBarWidth = 35; btnHideMinWidth = buttonContainerWidth(m_buttonsLeft, true) + buttonContainerWidth(m_buttonsRight, true) + layoutMetric(LM_TitleEdgeLeft, false) + layoutMetric(LM_TitleEdgeRight, false) + layoutMetric(LM_TitleBorderLeft, false) + layoutMetric(LM_TitleBorderRight, false) + minTitleBarWidth; btnHideLastWidth = 0; } void KCommonDecoration::objDestroyed(QObject *obj) { // make sure button deletion is reflected in the m_button[] array. // this makes sure that m_button[]-entries are not destroyed // twice, first in their KCommonDecorationButton/QObject // destructor (button objects are parented with the decroation // widget in KCommonDecoration constructor), and then in // ~KCommonDecoration. a QPointer would // have been the better approach, but changing the button array // would have been ABI incompatible & would have required creation // of kcommondecorationprivate instance. // the same applies to m_previewWidget. for (int n = 0; n < NumButtons; n++) { if (m_button[n] == obj) { m_button[n] = 0; break; } } if (obj == m_previewWidget) m_previewWidget = 0; } QRegion KCommonDecoration::region(KDecorationDefines::Region) { return QRegion(); } int KCommonDecoration::buttonsLeftWidth() const { return buttonContainerWidth(m_buttonsLeft); } int KCommonDecoration::buttonsRightWidth() const { return buttonContainerWidth(m_buttonsRight); } int KCommonDecoration::buttonContainerWidth(const ButtonContainer &btnContainer, bool countHidden) const { int explicitSpacer = layoutMetric(LM_ExplicitButtonSpacer); int shownElementsCount = 0; int w = 0; for (ButtonContainer::const_iterator it = btnContainer.begin(); it != btnContainer.end(); ++it) { if (*it) { if (countHidden || !(*it)->isHidden()) { w += (*it)->width(); ++shownElementsCount; } } else { w += explicitSpacer; ++shownElementsCount; } } w += layoutMetric(LM_ButtonSpacing) * (shownElementsCount - 1); return w; } void KCommonDecoration::addButtons(ButtonContainer &btnContainer, const QString& s, bool isLeft) { if (s.length() > 0) { for (int n = 0; n < s.length(); n++) { KCommonDecorationButton *btn = 0; switch(s[n].toAscii()) { case 'M': // Menu button if (!m_button[MenuButton]) { btn = createButton(MenuButton); if (!btn) break; btn->setTipText(i18nc("Button showing window actions menu", "Window Menu")); btn->setRealizeButtons(Qt::LeftButton | Qt::RightButton); connect(btn, SIGNAL(pressed()), SLOT(menuButtonPressed())); connect(btn, SIGNAL(released()), this, SLOT(menuButtonReleased())); // fix double deletion, see objDestroyed() connect(btn, SIGNAL(destroyed(QObject*)), this, SLOT(objDestroyed(QObject*))); m_button[MenuButton] = btn; } break; case 'N': // Application Menu button if (!m_button[AppMenuButton]) { btn = createButton(AppMenuButton); if (!btn) break; btn->setTipText(i18nc("Button showing application menu", "Application Menu")); btn->setRealizeButtons(Qt::LeftButton); connect(btn, SIGNAL(clicked()), SLOT(appMenuButtonPressed()), Qt::QueuedConnection); // Application want to show it menu connect(decoration(), SIGNAL(showRequest()), this, SLOT(appMenuButtonPressed()), Qt::UniqueConnection); // Wait for menu to become available before displaying any button connect(decoration(), SIGNAL(appMenuAvailable()), this, SLOT(slotAppMenuAvailable()), Qt::UniqueConnection); // On Kded module shutdown, hide application menu button connect(decoration(), SIGNAL(appMenuUnavailable()), this, SLOT(slotAppMenuUnavailable()), Qt::UniqueConnection); // Application menu button may need to be modified on this signal connect(decoration(), SIGNAL(menuHidden()), btn, SLOT(slotAppMenuHidden()), Qt::UniqueConnection); // fix double deletion, see objDestroyed() connect(btn, SIGNAL(destroyed(QObject*)), this, SLOT(objDestroyed(QObject*))); m_button[AppMenuButton] = btn; } break; case 'S': // OnAllDesktops button if (!m_button[OnAllDesktopsButton]) { btn = createButton(OnAllDesktopsButton); if (!btn) break; const bool oad = isOnAllDesktops(); btn->setTipText(oad ? i18n("Not on all desktops") : i18n("On all desktops")); btn->setToggleButton(true); btn->setOn(oad); connect(btn, SIGNAL(clicked()), SLOT(toggleOnAllDesktops())); // fix double deletion, see objDestroyed() connect(btn, SIGNAL(destroyed(QObject*)), this, SLOT(objDestroyed(QObject*))); m_button[OnAllDesktopsButton] = btn; } break; case 'H': // Help button if ((!m_button[HelpButton]) && providesContextHelp()) { btn = createButton(HelpButton); if (!btn) break; btn->setTipText(i18n("Help")); connect(btn, SIGNAL(clicked()), SLOT(showContextHelp())); // fix double deletion, see objDestroyed() connect(btn, SIGNAL(destroyed(QObject*)), this, SLOT(objDestroyed(QObject*))); m_button[HelpButton] = btn; } break; case 'I': // Minimize button if ((!m_button[MinButton]) && isMinimizable()) { btn = createButton(MinButton); if (!btn) break; btn->setTipText(i18n("Minimize")); connect(btn, SIGNAL(clicked()), SLOT(minimize())); // fix double deletion, see objDestroyed() connect(btn, SIGNAL(destroyed(QObject*)), this, SLOT(objDestroyed(QObject*))); m_button[MinButton] = btn; } break; case 'A': // Maximize button if ((!m_button[MaxButton]) && isMaximizable()) { btn = createButton(MaxButton); if (!btn) break; btn->setRealizeButtons(Qt::LeftButton | Qt::MidButton | Qt::RightButton); const bool max = maximizeMode() == MaximizeFull; btn->setTipText(max ? i18n("Restore") : i18n("Maximize")); btn->setToggleButton(true); btn->setOn(max); connect(btn, SIGNAL(clicked()), SLOT(slotMaximize())); // fix double deletion, see objDestroyed() connect(btn, SIGNAL(destroyed(QObject*)), this, SLOT(objDestroyed(QObject*))); m_button[MaxButton] = btn; } break; case 'X': // Close button if ((!m_button[CloseButton]) && isCloseable()) { btn = createButton(CloseButton); if (!btn) break; btn->setTipText(i18n("Close")); connect(btn, SIGNAL(clicked()), SLOT(closeWindow())); // fix double deletion, see objDestroyed() connect(btn, SIGNAL(destroyed(QObject*)), this, SLOT(objDestroyed(QObject*))); m_button[CloseButton] = btn; } break; case 'F': // AboveButton button if (!m_button[AboveButton]) { btn = createButton(AboveButton); if (!btn) break; bool above = keepAbove(); btn->setTipText(above ? i18n("Do not keep above others") : i18n("Keep above others")); btn->setToggleButton(true); btn->setOn(above); connect(btn, SIGNAL(clicked()), SLOT(slotKeepAbove())); // fix double deletion, see objDestroyed() connect(btn, SIGNAL(destroyed(QObject*)), this, SLOT(objDestroyed(QObject*))); m_button[AboveButton] = btn; } break; case 'B': // BelowButton button if (!m_button[BelowButton]) { btn = createButton(BelowButton); if (!btn) break; bool below = keepBelow(); btn->setTipText(below ? i18n("Do not keep below others") : i18n("Keep below others")); btn->setToggleButton(true); btn->setOn(below); connect(btn, SIGNAL(clicked()), SLOT(slotKeepBelow())); // fix double deletion, see objDestroyed() connect(btn, SIGNAL(destroyed(QObject*)), this, SLOT(objDestroyed(QObject*))); m_button[BelowButton] = btn; } break; case 'L': // Shade button if ((!m_button[ShadeButton]) && isShadeable()) { btn = createButton(ShadeButton); if (!btn) break; bool shaded = isSetShade(); btn->setTipText(shaded ? i18n("Unshade") : i18n("Shade")); btn->setToggleButton(true); btn->setOn(shaded); connect(btn, SIGNAL(clicked()), SLOT(slotShade())); // fix double deletion, see objDestroyed() connect(btn, SIGNAL(destroyed(QObject*)), this, SLOT(objDestroyed(QObject*))); m_button[ShadeButton] = btn; } break; case '_': // Spacer item btnContainer.append(0); } if (btn) { btn->setLeft(isLeft); btn->setSize(QSize(layoutMetric(LM_ButtonWidth, true, btn), layoutMetric(LM_ButtonHeight, true, btn))); // will be shown later on window registration if (btn->type() == AppMenuButton && !isPreview() && !wrapper->menuAvailable()) { btn->hide(); } else { btn->show(); } btnContainer.append(btn); } } } } void KCommonDecoration::calcHiddenButtons() { if (width() == btnHideLastWidth) return; btnHideLastWidth = width(); //Hide buttons in the following order: KCommonDecorationButton* btnArray[] = { m_button[HelpButton], m_button[AppMenuButton], m_button[ShadeButton], m_button[BelowButton], m_button[AboveButton], m_button[OnAllDesktopsButton], m_button[MaxButton], m_button[MinButton], m_button[MenuButton], m_button[CloseButton] }; const int buttonsCount = sizeof(btnArray) / sizeof(btnArray[ 0 ]); int current_width = width(); int count = 0; // Hide buttons while (current_width < btnHideMinWidth && count < buttonsCount) { if (btnArray[count]) { current_width += btnArray[count]->width(); if (btnArray[count]->isVisible()) btnArray[count]->hide(); } count++; } // Show the rest of the buttons... for (int i = count; i < buttonsCount; i++) { if (btnArray[i]) { if (! btnArray[i]->isHidden()) break; // all buttons shown... if (btnArray[i]->type() != AppMenuButton || wrapper->menuAvailable()) btnArray[i]->show(); } } } void KCommonDecoration::show() { if (decorationBehaviour(DB_ButtonHide)) calcHiddenButtons(); widget()->show(); } void KCommonDecoration::resize(const QSize& s) { widget()->resize(s); } QSize KCommonDecoration::minimumSize() const { const int minWidth = qMax(layoutMetric(LM_TitleEdgeLeft), layoutMetric(LM_BorderLeft)) + qMax(layoutMetric(LM_TitleEdgeRight), layoutMetric(LM_BorderRight)) + layoutMetric(LM_TitleBorderLeft) + layoutMetric(LM_TitleBorderRight); return QSize(minWidth, layoutMetric(LM_TitleEdgeTop) + layoutMetric(LM_TitleHeight) + layoutMetric(LM_TitleEdgeBottom) + layoutMetric(LM_BorderBottom)); } void KCommonDecoration::maximizeChange() { if (m_button[MaxButton]) { m_button[MaxButton]->setOn(maximizeMode() == MaximizeFull); m_button[MaxButton]->setTipText((maximizeMode() != MaximizeFull) ? i18n("Maximize") : i18n("Restore")); m_button[MaxButton]->reset(KCommonDecorationButton::StateChange); } updateWindowShape(); widget()->update(); } void KCommonDecoration::desktopChange() { if (m_button[OnAllDesktopsButton]) { m_button[OnAllDesktopsButton]->setOn(isOnAllDesktops()); m_button[OnAllDesktopsButton]->setTipText(isOnAllDesktops() ? i18n("Not on all desktops") : i18n("On all desktops")); m_button[OnAllDesktopsButton]->reset(KCommonDecorationButton::StateChange); } } void KCommonDecoration::shadeChange() { if (m_button[ShadeButton]) { bool shaded = isSetShade(); m_button[ShadeButton]->setOn(shaded); m_button[ShadeButton]->setTipText(shaded ? i18n("Unshade") : i18n("Shade")); m_button[ShadeButton]->reset(KCommonDecorationButton::StateChange); } } void KCommonDecoration::iconChange() { if (m_button[MenuButton]) { m_button[MenuButton]->update(); m_button[MenuButton]->reset(KCommonDecorationButton::IconChange); } } void KCommonDecoration::activeChange() { updateButtons(); widget()->update(); // do something similar to updateCaption here } void KCommonDecoration::captionChange() { updateCaption(); } void KCommonDecoration::keepAboveChange(bool above) { if (m_button[AboveButton]) { m_button[AboveButton]->setOn(above); m_button[AboveButton]->setTipText(above ? i18n("Do not keep above others") : i18n("Keep above others")); m_button[AboveButton]->reset(KCommonDecorationButton::StateChange); } if (m_button[BelowButton] && m_button[BelowButton]->isChecked()) { m_button[BelowButton]->setOn(false); m_button[BelowButton]->setTipText(i18n("Keep below others")); m_button[BelowButton]->reset(KCommonDecorationButton::StateChange); } } void KCommonDecoration::keepBelowChange(bool below) { if (m_button[BelowButton]) { m_button[BelowButton]->setOn(below); m_button[BelowButton]->setTipText(below ? i18n("Do not keep below others") : i18n("Keep below others")); m_button[BelowButton]->reset(KCommonDecorationButton::StateChange); } if (m_button[AboveButton] && m_button[AboveButton]->isChecked()) { m_button[AboveButton]->setOn(false); m_button[AboveButton]->setTipText(i18n("Keep above others")); m_button[AboveButton]->reset(KCommonDecorationButton::StateChange); } } void KCommonDecoration::slotMaximize() { if (m_button[MaxButton]) { maximize(m_button[MaxButton]->lastMousePress()); } } void KCommonDecoration::slotShade() { setShade(!isSetShade()); } void KCommonDecoration::slotKeepAbove() { setKeepAbove(!keepAbove()); } void KCommonDecoration::slotKeepBelow() { setKeepBelow(!keepBelow()); } static QBasicTimer* timer = NULL; void KCommonDecoration::menuButtonPressed() { if (decorationBehaviour(DB_MenuClose)) { if (timer == NULL) { timer = new QBasicTimer(); } if (!timer->isActive()) { timer->start(150, this); } // double click behavior static QTime* t = NULL; static KCommonDecoration* lastClient = NULL; if (t == NULL) { t = new QTime; } if (lastClient == this && t->elapsed() <= QApplication::doubleClickInterval()) { closing = true; } else { lastClient = this; t->start(); } } else { KDecorationFactory* f = factory(); doShowWindowMenu(); if (!f->exists(decoration())) // 'this' was deleted return; m_button[MenuButton]->setDown(false); } } void KCommonDecoration::menuButtonReleased() { if (closing) { if (timer && timer->isActive()) { timer->stop(); } closeWindow(); } } void KCommonDecoration::timerEvent(QTimerEvent *event) { if (timer && event->timerId() == timer->timerId()) { timer->stop(); if (closing || !m_button[MenuButton]->isDown()) { return; } closing = false; doShowWindowMenu(); return; } QObject::timerEvent(event); } void KCommonDecoration::doShowWindowMenu() { QRect menuRect = m_button[MenuButton]->rect(); QPoint menutop = m_button[MenuButton]->mapToGlobal(menuRect.topLeft()); QPoint menubottom = m_button[MenuButton]->mapToGlobal(menuRect.bottomRight()) + QPoint(0, 2); showWindowMenu(QRect(menutop, menubottom)); } void KCommonDecoration::appMenuButtonPressed() { QRect menuRect = m_button[AppMenuButton]->rect(); wrapper->showApplicationMenu(m_button[AppMenuButton]->mapToGlobal(menuRect.bottomLeft())); KDecorationFactory* f = factory(); if (!f->exists(decoration())) // 'this' was deleted return; m_button[AppMenuButton]->setDown(false); } void KCommonDecoration::slotAppMenuAvailable() { if (m_button[AppMenuButton]) { m_button[AppMenuButton]->show(); updateLayout(); } } void KCommonDecoration::slotAppMenuUnavailable() { if (m_button[AppMenuButton]) { m_button[AppMenuButton]->hide(); updateLayout(); } } void KCommonDecoration::resizeEvent(QResizeEvent */*e*/) { if (decorationBehaviour(DB_ButtonHide)) calcHiddenButtons(); updateLayout(); updateWindowShape(); // FIXME: don't update() here! this would result in two paintEvent()s // because there is already "something" else triggering the repaint... // widget()->update(); } void KCommonDecoration::moveWidget(int x, int y, QWidget *widget) const { QPoint p = widget->pos(); int oldX = p.y(); int oldY = p.x(); if (x != oldX || y != oldY) widget->move(x, y); } void KCommonDecoration::resizeWidget(int w, int h, QWidget *widget) const { QSize s = widget->size(); int oldW = s.width(); int oldH = s.height(); if (w != oldW || h != oldH) widget->resize(w, h); } void KCommonDecoration::mouseDoubleClickEvent(QMouseEvent *e) { if (e->button() != Qt::LeftButton) return; int tb = layoutMetric(LM_TitleEdgeTop) + layoutMetric(LM_TitleHeight) + layoutMetric(LM_TitleEdgeBottom) + layoutMetric(LM_OuterPaddingTop); // when shaded, react on double clicks everywhere to make it easier to unshade. otherwise // react only on double clicks in the title bar region... if (isSetShade() || e->pos().y() <= tb) titlebarDblClickOperation(); } void KCommonDecoration::wheelEvent(QWheelEvent *e) { int tb = layoutMetric(LM_TitleEdgeTop) + layoutMetric(LM_TitleHeight) + layoutMetric(LM_TitleEdgeBottom) + layoutMetric(LM_OuterPaddingTop); if (isSetShade() || e->pos().y() <= tb) titlebarMouseWheelOperation(e->delta()); } KCommonDecoration::Position KCommonDecoration::mousePosition(const QPoint &point) const { const int corner = 18 + 3 * layoutMetric(LM_BorderBottom, false) / 2; Position pos = PositionCenter; const int paddingLeft = layoutMetric(LM_OuterPaddingLeft); const int paddingTop = layoutMetric(LM_OuterPaddingTop); const int paddingRight = layoutMetric(LM_OuterPaddingRight); const int paddingBottom = layoutMetric(LM_OuterPaddingBottom); QRect r = widget()->rect().adjusted(paddingLeft, paddingTop, -paddingRight, -paddingBottom); int r_x, r_y, r_x2, r_y2; r.getCoords(&r_x, &r_y, &r_x2, &r_y2); int p_x = point.x(); int p_y = point.y(); const int borderLeft = layoutMetric(LM_BorderLeft); // const int borderRight = layoutMetric(LM_BorderRight); const int borderBottom = layoutMetric(LM_BorderBottom); const int titleHeight = layoutMetric(LM_TitleHeight); const int titleEdgeTop = layoutMetric(LM_TitleEdgeTop); const int titleEdgeBottom = layoutMetric(LM_TitleEdgeBottom); const int titleEdgeLeft = layoutMetric(LM_TitleEdgeLeft); const int titleEdgeRight = layoutMetric(LM_TitleEdgeRight); const int borderBottomTop = r_y2 - borderBottom + 1; const int borderLeftRight = r_x + borderLeft - 1; // const int borderRightLeft = r_x2-borderRight+1; const int titleEdgeLeftRight = r_x + titleEdgeLeft - 1; const int titleEdgeRightLeft = r_x2 - titleEdgeRight + 1; const int titleEdgeBottomBottom = r_y + titleEdgeTop + titleHeight + titleEdgeBottom - 1; const int titleEdgeTopBottom = r_y + titleEdgeTop - 1; if (p_y <= titleEdgeTopBottom) { if (p_x <= r_x + corner) pos = PositionTopLeft; else if (p_x >= r_x2 - corner) pos = PositionTopRight; else pos = PositionTop; } else if (p_y <= titleEdgeBottomBottom) { if (p_x <= titleEdgeLeftRight) pos = PositionTopLeft; else if (p_x >= titleEdgeRightLeft) pos = PositionTopRight; else pos = PositionCenter; // title bar } else if (p_y < borderBottomTop) { if (p_y < r_y2 - corner) { if (p_x <= borderLeftRight) pos = PositionLeft; else pos = PositionRight; } else { if (p_x <= borderLeftRight) pos = PositionBottomLeft; else pos = PositionBottomRight; } } else if (p_y >= borderBottomTop) { if (p_x <= r_x + corner) pos = PositionBottomLeft; else if (p_x >= r_x2 - corner) pos = PositionBottomRight; else pos = PositionBottom; } return pos; } void KCommonDecoration::updateWindowShape() { // don't mask the widget... if (!decorationBehaviour(DB_WindowMask)) return; int w = widget()->width(); int h = widget()->height(); QRegion mask(0, 0, w, h); // Remove corners (if subclass wants to.) mask -= cornerShape(WC_TopLeft); mask -= cornerShape(WC_TopRight); mask -= cornerShape(WC_BottomLeft); mask -= cornerShape(WC_BottomRight); setMask(mask); } bool KCommonDecoration::eventFilter(QObject* o, QEvent* e) { if (o != widget()) return false; switch(e->type()) { case QEvent::Resize: resizeEvent(static_cast(e)); return true; case QEvent::Paint: paintEvent(static_cast< QPaintEvent* >(e)); return true; case QEvent::MouseButtonDblClick: mouseDoubleClickEvent(static_cast< QMouseEvent* >(e)); return true; case QEvent::MouseButtonPress: processMousePressEvent(static_cast< QMouseEvent* >(e)); return true; case QEvent::Wheel: wheelEvent(static_cast< QWheelEvent* >(e)); return true; default: return false; } } const int SUPPORTED_WINDOW_TYPES_MASK = NET::NormalMask | NET::DesktopMask | NET::DockMask | NET::ToolbarMask | NET::MenuMask | NET::DialogMask /*| NET::OverrideMask*/ | NET::TopMenuMask | NET::UtilityMask | NET::SplashMask; bool KCommonDecoration::isToolWindow() const { NET::WindowType type = windowType(SUPPORTED_WINDOW_TYPES_MASK); return ((type == NET::Toolbar) || (type == NET::Utility) || (type == NET::Menu)); } QRect KCommonDecoration::titleRect() const { const int pl = layoutMetric(LM_OuterPaddingLeft); const int pt = layoutMetric(LM_OuterPaddingTop); const int pr = layoutMetric(LM_OuterPaddingRight); const int pb = layoutMetric(LM_OuterPaddingBottom); int r_x, r_y, r_x2, r_y2; widget()->rect().adjusted(pl, pt, -pr, -pb).getCoords(&r_x, &r_y, &r_x2, &r_y2); const int titleEdgeLeft = layoutMetric(LM_TitleEdgeLeft); const int titleEdgeTop = layoutMetric(LM_TitleEdgeTop); const int titleEdgeRight = layoutMetric(LM_TitleEdgeRight); const int titleEdgeBottom = layoutMetric(LM_TitleEdgeBottom); const int titleBorderLeft = layoutMetric(LM_TitleBorderLeft); const int titleBorderRight = layoutMetric(LM_TitleBorderRight); const int ttlHeight = layoutMetric(LM_TitleHeight); const int titleEdgeBottomBottom = r_y + titleEdgeTop + ttlHeight + titleEdgeBottom - 1; return QRect(r_x + titleEdgeLeft + buttonsLeftWidth() + titleBorderLeft, r_y + titleEdgeTop, r_x2 - titleEdgeRight - buttonsRightWidth() - titleBorderRight - (r_x + titleEdgeLeft + buttonsLeftWidth() + titleBorderLeft), titleEdgeBottomBottom - (r_y + titleEdgeTop)); } QRect KCommonDecoration::transparentRect() const { return wrapper->transparentRect(); } KCommonDecorationButton::KCommonDecorationButton(ButtonType type, KCommonDecoration *parent) : QAbstractButton(parent ? parent->widget() : 0), m_decoration(parent), m_type(type), m_realizeButtons(Qt::LeftButton), m_lastMouse(Qt::NoButton), m_isLeft(true) { setCursor(Qt::ArrowCursor); } KCommonDecorationButton::~KCommonDecorationButton() { } KCommonDecoration *KCommonDecorationButton::decoration() const { return m_decoration; } ButtonType KCommonDecorationButton::type() const { return m_type; } bool KCommonDecorationButton::isLeft() const { return m_isLeft; } void KCommonDecorationButton::setLeft(bool left) { m_isLeft = left; } void KCommonDecorationButton::setRealizeButtons(int btns) { m_realizeButtons = btns; } void KCommonDecorationButton::setSize(const QSize &s) { if (!m_size.isValid() || s != size()) { m_size = s; setFixedSize(m_size); reset(SizeChange); } } QSize KCommonDecorationButton::sizeHint() const { return m_size; } void KCommonDecorationButton::setTipText(const QString &tip) { this->setToolTip(tip); } void KCommonDecorationButton::setToggleButton(bool toggle) { QAbstractButton::setCheckable(toggle); reset(ToggleChange); } void KCommonDecorationButton::setOn(bool on) { if (on != isChecked()) { QAbstractButton::setChecked(on); reset(StateChange); } } void KCommonDecorationButton::mousePressEvent(QMouseEvent* e) { m_lastMouse = e->button(); // pass on event after changing button to LeftButton QMouseEvent me(e->type(), e->pos(), (e->button()&m_realizeButtons) ? Qt::LeftButton : Qt::NoButton, e->buttons(), e->modifiers()); QAbstractButton::mousePressEvent(&me); } void KCommonDecorationButton::mouseReleaseEvent(QMouseEvent* e) { m_lastMouse = e->button(); // pass on event after changing button to LeftButton QMouseEvent me(e->type(), e->pos(), (e->button()&m_realizeButtons) ? Qt::LeftButton : Qt::NoButton, e->buttons(), e->modifiers()); QAbstractButton::mouseReleaseEvent(&me); } // *** wrap everything from KDecoration *** // bool KCommonDecoration::drawbound(const QRect&, bool) { return false; } bool KCommonDecoration::windowDocked(Position) { return false; } const KDecorationOptions* KCommonDecoration::options() { return KDecoration::options(); } bool KCommonDecoration::isActive() const { return wrapper->isActive(); } bool KCommonDecoration::isCloseable() const { return wrapper->isCloseable(); } bool KCommonDecoration::isMaximizable() const { return wrapper->isMaximizable(); } KCommonDecoration::MaximizeMode KCommonDecoration::maximizeMode() const { return wrapper->maximizeMode(); } bool KCommonDecoration::isMinimizable() const { return wrapper->isMinimizable(); } bool KCommonDecoration::providesContextHelp() const { return wrapper->providesContextHelp(); } int KCommonDecoration::desktop() const { return wrapper->desktop(); } bool KCommonDecoration::isOnAllDesktops() const { return wrapper->isOnAllDesktops(); } bool KCommonDecoration::isModal() const { return wrapper->isModal(); } bool KCommonDecoration::isShadeable() const { return wrapper->isShadeable(); } bool KCommonDecoration::isShade() const { return wrapper->isShade(); } bool KCommonDecoration::isSetShade() const { return wrapper->isSetShade(); } bool KCommonDecoration::keepAbove() const { return wrapper->keepAbove(); } bool KCommonDecoration::keepBelow() const { return wrapper->keepBelow(); } bool KCommonDecoration::isMovable() const { return wrapper->isMovable(); } bool KCommonDecoration::isResizable() const { return wrapper->isResizable(); } NET::WindowType KCommonDecoration::windowType(unsigned long supported_types) const { return wrapper->windowType(supported_types); } QIcon KCommonDecoration::icon() const { return wrapper->icon(); } QString KCommonDecoration::caption() const { return wrapper->caption(); } void KCommonDecoration::showWindowMenu(const QRect &pos) { return wrapper->showWindowMenu(pos); } void KCommonDecoration::showWindowMenu(QPoint pos) { return wrapper->showWindowMenu(pos); } void KCommonDecoration::performWindowOperation(WindowOperation op) { return wrapper->performWindowOperation(op); } void KCommonDecoration::setMask(const QRegion& reg, int mode) { return wrapper->setMask(reg, mode); } void KCommonDecoration::clearMask() { return wrapper->clearMask(); } bool KCommonDecoration::isPreview() const { return wrapper->isPreview(); } QRect KCommonDecoration::geometry() const { return wrapper->geometry(); } QRect KCommonDecoration::iconGeometry() const { return wrapper->iconGeometry(); } QRegion KCommonDecoration::unobscuredRegion(const QRegion& r) const { return wrapper->unobscuredRegion(r); } WId KCommonDecoration::windowId() const { return wrapper->windowId(); } int KCommonDecoration::width() const { return wrapper->width(); } int KCommonDecoration::height() const { return wrapper->height(); } void KCommonDecoration::processMousePressEvent(QMouseEvent* e) { return wrapper->processMousePressEvent(e); } void KCommonDecoration::setMainWidget(QWidget* w) { return wrapper->setMainWidget(w); } void KCommonDecoration::createMainWidget(Qt::WindowFlags flags) { return wrapper->createMainWidget(flags); } QWidget* KCommonDecoration::initialParentWidget() const { return wrapper->initialParentWidget(); } Qt::WindowFlags KCommonDecoration::initialWFlags() const { return wrapper->initialWFlags(); } QWidget* KCommonDecoration::widget() { return wrapper->widget(); } const QWidget* KCommonDecoration::widget() const { return wrapper->widget(); } KDecorationFactory* KCommonDecoration::factory() const { return wrapper->factory(); } void KCommonDecoration::grabXServer() { return wrapper->grabXServer(); } void KCommonDecoration::ungrabXServer() { return wrapper->ungrabXServer(); } void KCommonDecoration::closeWindow() { return wrapper->closeWindow(); } void KCommonDecoration::maximize(Qt::MouseButtons button) { return wrapper->maximize(button); } void KCommonDecoration::maximize(MaximizeMode mode) { return wrapper->maximize(mode); } void KCommonDecoration::minimize() { return wrapper->minimize(); } void KCommonDecoration::showContextHelp() { return wrapper->showContextHelp(); } void KCommonDecoration::setDesktop(int desktop) { return wrapper->setDesktop(desktop); } void KCommonDecoration::toggleOnAllDesktops() { return wrapper->toggleOnAllDesktops(); } void KCommonDecoration::titlebarDblClickOperation() { return wrapper->titlebarDblClickOperation(); } void KCommonDecoration::titlebarMouseWheelOperation(int delta) { return wrapper->titlebarMouseWheelOperation(delta); } void KCommonDecoration::setShade(bool set) { return wrapper->setShade(set); } void KCommonDecoration::setKeepAbove(bool set) { return wrapper->setKeepAbove(set); } void KCommonDecoration::setKeepBelow(bool set) { return wrapper->setKeepBelow(set); } void KCommonDecoration::setAlphaEnabled(bool enabled) { wrapper->wrapSetAlphaEnabled(enabled); } // *** end of wrapping of everything from KDecoration *** // const KDecoration* KCommonDecoration::decoration() const { return wrapper; } KDecoration* KCommonDecoration::decoration() { return wrapper; } - -KCommonDecorationUnstable::KCommonDecorationUnstable(KDecorationBridge* bridge, KDecorationFactory* factory) - : KCommonDecoration(bridge, factory) -{ - Q_ASSERT(dynamic_cast(decoration())); -} - -KCommonDecorationUnstable::~KCommonDecorationUnstable() -{ -} - // All copied from kdecoration.cpp -bool KCommonDecorationUnstable::compositingActive() const +bool KCommonDecoration::compositingActive() const { - return static_cast(decoration())->compositingActive(); + return decoration()->compositingActive(); } // Window tabbing -int KCommonDecorationUnstable::tabCount() const -{ - return static_cast(decoration())->tabCount(); -} - -QString KCommonDecorationUnstable::caption(int idx) const +int KCommonDecoration::tabCount() const { - return static_cast(decoration())->caption(idx); + return decoration()->tabCount(); } -QIcon KCommonDecorationUnstable::icon(int idx) const +QString KCommonDecoration::caption(int idx) const { - return static_cast(decoration())->icon(idx); + return decoration()->caption(idx); } -long KCommonDecorationUnstable::tabId(int idx) const +QIcon KCommonDecoration::icon(int idx) const { - return static_cast(decoration())->tabId(idx); + return decoration()->icon(idx); } -long KCommonDecorationUnstable::currentTabId() const +long KCommonDecoration::tabId(int idx) const { - return static_cast(decoration())->currentTabId(); + return decoration()->tabId(idx); } -void KCommonDecorationUnstable::setCurrentTab(long id) +long KCommonDecoration::currentTabId() const { - static_cast(decoration())->setCurrentTab(id); + return decoration()->currentTabId(); } -void KCommonDecorationUnstable::tab_A_before_B(long A, long B) +void KCommonDecoration::setCurrentTab(long id) { - static_cast(decoration())->tab_A_before_B(A, B); + decoration()->setCurrentTab(id); } -void KCommonDecorationUnstable::tab_A_behind_B(long A, long B) +void KCommonDecoration::tab_A_before_B(long A, long B) { - static_cast(decoration())->tab_A_behind_B(A, B); + decoration()->tab_A_before_B(A, B); } -void KCommonDecorationUnstable::untab(long id, const QRect& newGeom) +void KCommonDecoration::tab_A_behind_B(long A, long B) { - static_cast(decoration())->untab(id, newGeom); + decoration()->tab_A_behind_B(A, B); } -void KCommonDecorationUnstable::closeTab(long id) +void KCommonDecoration::untab(long id, const QRect& newGeom) { - static_cast(decoration())->closeTab(id); + decoration()->untab(id, newGeom); } -void KCommonDecorationUnstable::closeTabGroup() +void KCommonDecoration::closeTab(long id) { - static_cast(decoration())->closeTabGroup(); + decoration()->closeTab(id); } -void KCommonDecorationUnstable::showWindowMenu(const QPoint &pos, long id) +void KCommonDecoration::closeTabGroup() { - static_cast(decoration())->showWindowMenu(pos, id); + decoration()->closeTabGroup(); } -KDecoration::WindowOperation KCommonDecorationUnstable::buttonToWindowOperation(Qt::MouseButtons button) +void KCommonDecoration::showWindowMenu(const QPoint &pos, long id) { - return static_cast(decoration())->buttonToWindowOperation(button); + decoration()->showWindowMenu(pos, id); } -bool KCommonDecorationUnstable::eventFilter(QObject* o, QEvent* e) +KDecoration::WindowOperation KCommonDecoration::buttonToWindowOperation(Qt::MouseButtons button) { - return KCommonDecoration::eventFilter(o, e); + return decoration()->buttonToWindowOperation(button); } // kate: space-indent on; indent-width 4; mixedindent off; indent-mode cstyle; diff --git a/kwin/libkdecorations/kcommondecoration.h b/kwin/libkdecorations/kcommondecoration.h index b19a41f366..3992e5dcb6 100644 --- a/kwin/libkdecorations/kcommondecoration.h +++ b/kwin/libkdecorations/kcommondecoration.h @@ -1,534 +1,525 @@ /* This file is part of the KDE project. Copyright (C) 2005 Sandro Giessl Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef KCOMMONDECORATION_H #define KCOMMONDECORATION_H #include #include "kdecoration.h" /** @addtogroup kdecoration */ /** @{ */ class KDecorationBridge; class KDecorationFactory; enum ButtonType { HelpButton = 0, MaxButton, MinButton, CloseButton, MenuButton, OnAllDesktopsButton, AboveButton, BelowButton, ShadeButton, AppMenuButton, NumButtons, ItemCloseButton = 100, // Close only one tab ItemMenuButton // shows the window menu for one tab }; class KCommonDecorationButton; class KCommonDecorationButtonPrivate; class KCommonDecorationPrivate; class KCommonDecorationWrapper; /** * This class eases development of decorations by implementing parts of KDecoration * which are error prone and common for most decorations. * It takes care of the window layout, button/action handling, and window mask creation. * Note that for technical reasons KCommonDecoration does not inherit KDecoration but * only provides the same API. If in rare cases you need to convert to KDecoration, * use the decoration() function. * See KDecoration documentation for all the wrapped functions. */ class KWIN_EXPORT KCommonDecoration : public QObject, public KDecorationDefines { Q_OBJECT public: KCommonDecoration(KDecorationBridge* bridge, KDecorationFactory* factory); virtual ~KCommonDecoration(); /** * Used to calculate the decoration layout. The basic layout looks like this: * * Window: * _______________________________________________________________ * | LM_TitleEdgeTop | * |_______________________________________________________________| * | LM_TitleEdgeLeft | [title] | LM_TitleEdgeRight | * |__________________|________________________|___________________| * | LM_TitleEdgeBottom | * |_______________________________________________________________| * | | | | * | | | | * | | | | * |LM_BorderLeft LM_BorderRight| * |_|___________________________________________________________|_| * | LM_BorderBottom | * |_______________________________________________________________| * * Title: * ___________________________________________________________________________________ * | LM_ButtonMarginTop | | LM_ButtonMarginTop | * |________________________________| |_________________________________| * | [Buttons] | LM_TitleBorderLeft | LM_TitleHeight | LM_TitleBorderRight | [Buttons] | * |___________|____________________|________________|_____________________|___________| * * Buttons: * _____________________________________________________________________________________________ * | button | spacing | button | spacing | explicit spacer | spacing | ... | spacing | button | * |________|_________|________|_________|_________________|_________|________|_________|________| * * @see layoutMetric() */ enum LayoutMetric { LM_BorderLeft, LM_BorderRight, LM_BorderBottom, LM_TitleHeight, LM_TitleBorderLeft, LM_TitleBorderRight, LM_TitleEdgeLeft, LM_TitleEdgeRight, LM_TitleEdgeTop, LM_TitleEdgeBottom, LM_ButtonWidth, LM_ButtonHeight, LM_ButtonSpacing, LM_ExplicitButtonSpacer, LM_ButtonMarginTop, LM_OuterPaddingLeft, ///< @since 4.3 LM_OuterPaddingTop, ///< @since 4.3 LM_OuterPaddingRight, ///< @since 4.3 LM_OuterPaddingBottom ///< @since 4.4 }; enum DecorationBehaviour { DB_MenuClose, ///< Close window on double clicking the menu DB_WindowMask, ///< Set a mask on the window DB_ButtonHide ///< Hide buttons when there is not enough space in the titlebar }; enum WindowCorner { WC_TopLeft, WC_TopRight, WC_BottomLeft, WC_BottomRight }; /** * The name of the decoration used in the decoration preview. */ virtual QString visibleName() const = 0; /** * The default title button order on the left. * @see KDecoration::titleButtonsLeft() * @see KDecoration::titleButtonsRight() */ virtual QString defaultButtonsLeft() const; /** * The default title button order on the left. * @see KDecoration::titleButtonsLeft() * @see KDecoration::titleButtonsRight() */ virtual QString defaultButtonsRight() const; /** * This controls whether some specific behaviour should be enabled or not. * @see DecorationBehaviour */ virtual bool decorationBehaviour(DecorationBehaviour behaviour) const; /** * This controls the layout of the decoration in various ways. It is * possible to have a different layout for different window states. * @param lm The layout element. * @param respectWindowState Whether window states should be taken into account or a "default" state should be assumed. * @param button For LM_ButtonWidth and LM_ButtonHeight, the button. */ virtual int layoutMetric(LayoutMetric lm, bool respectWindowState = true, const KCommonDecorationButton *button = 0) const; /** * Create a new title bar button. KCommonDecoration takes care of memory management. * @return a pointer to the button, or 0 if the button should not be created. */ virtual KCommonDecorationButton *createButton(ButtonType type) = 0; /** * @return the mask for the specific window corner. */ virtual QRegion cornerShape(WindowCorner corner); /** * This updates the window mask using the information provided by * cornerShape(). Edges which are aligned to screen corners are not * shaped for better usability (remember to paint these areas in paintEvent(), too). * You normally don't want/need to reimplement updateWindowShape(). * @see cornerShape() */ virtual void updateWindowShape(); /** * Draw the window decoration. */ virtual void paintEvent(QPaintEvent *e) = 0; /** * This is used to update the painting of the title bar after the caption has been changed. * Reimplement for a more efficient implementation (default calls update() on the whole decoration). */ virtual void updateCaption(); int buttonsLeftWidth() const; int buttonsRightWidth() const; /** * TODO: remove? */ void updateLayout() const; /** * Makes sure all buttons are repainted. */ void updateButtons() const; /** * Manually call reset() on each button. */ void resetButtons() const; /** * Convenience method. * @returns true if the window type is NET::Toolbar, NET::Utility, or NET::Menu */ bool isToolWindow() const; /** * Convenience method. * @returns the title rect. */ QRect titleRect() const; /** * Returns the rectangle within the decoration that should be transparent. * * Usually this is the rectangle occupied by the client window, * but a smaller rectangle may be returned if the client has specified * that the window decoration should extend below the client area. * * If the client has requested that the decoration should cover the whole * client area, a null rectangle is returned. * * The returned rectangle is never larger than the client area. * * @since 4.4 */ QRect transparentRect() const; public: /** * Handles widget and layout creation, call the base implementation when subclassing this member. */ virtual void init(); /** * Handles SettingButtons, call the base implementation when subclassing this member. */ virtual void reset(unsigned long changed); virtual void borders(int& left, int& right, int& top, int& bottom) const; virtual void show(); virtual void resize(const QSize& s); virtual QSize minimumSize() const; virtual void maximizeChange(); virtual void desktopChange(); virtual void shadeChange(); virtual void iconChange(); virtual void activeChange(); virtual void captionChange(); public Q_SLOTS: void keepAboveChange(bool above); void keepBelowChange(bool below); void slotMaximize(); void slotShade(); void slotKeepAbove(); void slotKeepBelow(); void menuButtonPressed(); void menuButtonReleased(); void appMenuButtonPressed(); void slotAppMenuAvailable(); void slotAppMenuUnavailable(); public: virtual Position mousePosition(const QPoint &point) const; virtual bool eventFilter(QObject* o, QEvent* e); virtual void resizeEvent(QResizeEvent *e); virtual void mouseDoubleClickEvent(QMouseEvent *e); virtual void wheelEvent(QWheelEvent *e); // *** wrap everything from KDecoration *** // // reimplementing from KDecoration (wrapped) virtual bool drawbound(const QRect& geom, bool clear); virtual bool windowDocked(Position side); // wrap everything KDecoration provides static const KDecorationOptions* options(); bool isActive() const; bool isCloseable() const; bool isMaximizable() const; MaximizeMode maximizeMode() const; bool isMinimizable() const; bool providesContextHelp() const; int desktop() const; bool isOnAllDesktops() const; // convenience bool isModal() const; bool isShadeable() const; bool isShade() const; bool isSetShade() const; bool keepAbove() const; bool keepBelow() const; bool isMovable() const; bool isResizable() const; NET::WindowType windowType(unsigned long supported_types) const; QIcon icon() const; QString caption() const; void showWindowMenu(const QRect &pos); void showWindowMenu(QPoint pos); void performWindowOperation(WindowOperation op); void setMask(const QRegion& reg, int mode = 0); void clearMask(); // convenience bool isPreview() const; QRect geometry() const; QRect iconGeometry() const; QRegion unobscuredRegion(const QRegion& r) const; WId windowId() const; int width() const; // convenience int height() const; // convenience void processMousePressEvent(QMouseEvent* e); + + bool compositingActive() const; + + // Window tabbing + QString caption(int idx) const; + void closeTab(long id); + void closeTabGroup(); + long currentTabId() const; + QIcon icon(int idx) const; + void setCurrentTab(long id); + void showWindowMenu(const QPoint &, long id); + void tab_A_before_B(long A, long B); + void tab_A_behind_B(long A, long B); + int tabCount() const; + long tabId(int idx) const; + void untab(long id, const QRect& newGeom); + + WindowOperation buttonToWindowOperation(Qt::MouseButtons button); + Q_SIGNALS: void keepAboveChanged(bool); void keepBelowChanged(bool); public: void setMainWidget(QWidget*); void createMainWidget(Qt::WindowFlags flags = 0); QWidget* initialParentWidget() const; Qt::WindowFlags initialWFlags() const; QWidget* widget(); const QWidget* widget() const; KDecorationFactory* factory() const; void grabXServer(); void ungrabXServer(); public Q_SLOTS: void closeWindow(); void maximize(Qt::MouseButtons button); void maximize(MaximizeMode mode); void minimize(); void showContextHelp(); void setDesktop(int desktop); void toggleOnAllDesktops(); // convenience void titlebarDblClickOperation(); void titlebarMouseWheelOperation(int delta); void setShade(bool set); void setKeepAbove(bool set); void setKeepBelow(bool set); // *** end of wrapping of everything from KDecoration *** // public: // access the KDecoration wrapper const KDecoration* decoration() const; KDecoration* decoration(); protected: virtual void timerEvent(QTimerEvent *event); protected Q_SLOTS: /** * A decoration providing AbilityAnnounceAlphaChannel can use this method to enable/disable the * use of alpha channel. This is useful if for a normal window the decoration renders its own * shadows or round corners and thus needs alpha channel. But in maximized state the decoration * is fully opaque. By disabling the alpha channel the Compositor can optimize the rendering. * * @param enabled If @c true alpha channel is enabled, if @c false alpha channel is disabled * @see KDecoration::setAlphaEnabled * @since 4.10 **/ void setAlphaEnabled(bool enabled); private Q_SLOTS: /* look out for buttons that have been destroyed. */ void objDestroyed(QObject *obj); /** * This slot can be reimplemented to return the regions defined * by KDecorationDefines::Region. * * The default implementation always returns an empty region. * * @since 4.10 */ QRegion region(KDecorationDefines::Region r); private: void resetLayout(); void moveWidget(int x, int y, QWidget *widget) const; void resizeWidget(int w, int h, QWidget *widget) const; void doShowWindowMenu(); typedef QVector ButtonContainer; ///< If the entry is 0, it's a spacer. int buttonContainerWidth(const ButtonContainer &btnContainer, bool countHidden = false) const; void addButtons(ButtonContainer &btnContainer, const QString& buttons, bool isLeft); KCommonDecorationButton *m_button[NumButtons]; ButtonContainer m_buttonsLeft; ButtonContainer m_buttonsRight; QWidget *m_previewWidget; // button hiding for small windows void calcHiddenButtons(); int btnHideMinWidth; int btnHideLastWidth; bool closing; // for menu doubleclick closing... KCommonDecorationWrapper* wrapper; KCommonDecorationPrivate *d; }; -class KWIN_EXPORT KCommonDecorationUnstable - : public KCommonDecoration -{ - Q_OBJECT -public: - KCommonDecorationUnstable(KDecorationBridge* bridge, KDecorationFactory* factory); - virtual ~KCommonDecorationUnstable(); - bool compositingActive() const; - - // Window tabbing - using KCommonDecoration::caption; - QString caption(int idx) const; - void closeTab(long id); - void closeTabGroup(); - long currentTabId() const; - QIcon icon(int idx = 0) const; - void setCurrentTab(long id); - void showWindowMenu(const QPoint &, long id); - void tab_A_before_B(long A, long B); - void tab_A_behind_B(long A, long B); - int tabCount() const; - long tabId(int idx) const; - void untab(long id, const QRect& newGeom); - - WindowOperation buttonToWindowOperation(Qt::MouseButtons button); - virtual bool eventFilter(QObject* o, QEvent* e); -}; - /** * Title bar buttons of KCommonDecoration need to inherit this class. */ class KWIN_EXPORT KCommonDecorationButton : public QAbstractButton { Q_OBJECT friend class KCommonDecoration; public: KCommonDecorationButton(ButtonType type, KCommonDecoration *parent); virtual ~KCommonDecorationButton(); /** * These flags specify what has changed, e.g. the reason for a reset(). */ enum { ManualReset = 1 << 0, ///< The button might want to do a full reset for some reason... SizeChange = 1 << 1, ///< The button size changed @see setSize() ToggleChange = 1 << 2, ///< The button toggle state has changed @see setToggleButton() StateChange = 1 << 3, ///< The button has been set pressed or not... @see setOn() IconChange = 1 << 4, ///< The window icon has been changed DecorationReset = 1 << 5 ///< E.g. when decoration colors have changed }; /** * Initialize the button after size change etc. */ virtual void reset(unsigned long changed) = 0; /** * @returns the KCommonDecoration the button belongs to. */ KCommonDecoration *decoration() const; /** * @returns the button type. * @see ButtonType */ ButtonType type() const; /** * Whether the button is left of the titlebar or not. */ bool isLeft() const; /** * Set which mouse buttons the button should honor. Used e.g. to prevent accidental right mouse clicks. */ void setRealizeButtons(int btns); /** * Set the button size. */ void setSize(const QSize &s); /** * Set/update the button's tool tip */ void setTipText(const QString &tip); /** * The mouse button that has been clicked last time. */ Qt::MouseButtons lastMousePress() const { return m_lastMouse; } QSize sizeHint() const; protected: void setToggleButton(bool toggle); void setOn(bool on); void setLeft(bool left); void mousePressEvent(QMouseEvent *e); void mouseReleaseEvent(QMouseEvent *e); private: KCommonDecoration *m_decoration; ButtonType m_type; int m_realizeButtons; QSize m_size; Qt::MouseButtons m_lastMouse; bool m_isLeft; KCommonDecorationButtonPrivate *d; }; /** @} */ #endif // KCOMMONDECORATION_H // kate: space-indent on; indent-width 4; mixedindent off; indent-mode cstyle; diff --git a/kwin/libkdecorations/kcommondecoration_p.cpp b/kwin/libkdecorations/kcommondecoration_p.cpp index 584eaed2fb..66b74a5e9d 100644 --- a/kwin/libkdecorations/kcommondecoration_p.cpp +++ b/kwin/libkdecorations/kcommondecoration_p.cpp @@ -1,137 +1,137 @@ /* This file is part of the KDE project. Copyright (C) 2007 Lubos Lunak Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "kcommondecoration_p.h" #include "kcommondecoration.h" #include "kdecorationbridge.h" #include #include "kcommondecoration_p.moc" KCommonDecorationWrapper::KCommonDecorationWrapper(KCommonDecoration* deco, KDecorationBridge* bridge, KDecorationFactory* factory) - : KDecorationUnstable(bridge, factory) + : KDecoration(bridge, factory) , decoration(deco) { } KCommonDecorationWrapper::~KCommonDecorationWrapper() { // the wrapper actually owns KCommonDecoration, since the wrapper is what KWin core uses delete decoration; } void KCommonDecorationWrapper::init() { return decoration->init(); } KCommonDecorationWrapper::Position KCommonDecorationWrapper::mousePosition(const QPoint& p) const { return decoration->mousePosition(p); } void KCommonDecorationWrapper::borders(int& left, int& right, int& top, int& bottom) const { return decoration->borders(left, right, top, bottom); } void KCommonDecorationWrapper::resize(const QSize& s) { return decoration->resize(s); } QSize KCommonDecorationWrapper::minimumSize() const { return decoration->minimumSize(); } void KCommonDecorationWrapper::activeChange() { return decoration->activeChange(); } void KCommonDecorationWrapper::captionChange() { return decoration->captionChange(); } void KCommonDecorationWrapper::iconChange() { return decoration->iconChange(); } void KCommonDecorationWrapper::maximizeChange() { return decoration->maximizeChange(); } void KCommonDecorationWrapper::desktopChange() { return decoration->desktopChange(); } void KCommonDecorationWrapper::shadeChange() { return decoration->shadeChange(); } bool KCommonDecorationWrapper::drawbound(const QRect& geom, bool clear) { return decoration->drawbound(geom, clear); } bool KCommonDecorationWrapper::windowDocked(Position side) { return decoration->windowDocked(side); } void KCommonDecorationWrapper::reset(unsigned long changed) { return decoration->reset(changed); } void KCommonDecorationWrapper::padding(int &left, int &right, int &top, int &bottom) const { left = decoration->layoutMetric(KCommonDecoration::LM_OuterPaddingLeft); right = decoration->layoutMetric(KCommonDecoration::LM_OuterPaddingRight); top = decoration->layoutMetric(KCommonDecoration::LM_OuterPaddingTop); bottom = decoration->layoutMetric(KCommonDecoration::LM_OuterPaddingBottom); } void KCommonDecorationWrapper::wrapSetAlphaEnabled(bool enabled) { setAlphaEnabled(enabled); } QRegion KCommonDecorationWrapper::region(KDecorationDefines::Region r) { QRegion region; QMetaObject::invokeMethod(decoration, "region", Qt::DirectConnection, Q_RETURN_ARG(QRegion, region), Q_ARG(KDecorationDefines::Region, r)); return region; } diff --git a/kwin/libkdecorations/kcommondecoration_p.h b/kwin/libkdecorations/kcommondecoration_p.h index df4d4b398c..68662bd38e 100644 --- a/kwin/libkdecorations/kcommondecoration_p.h +++ b/kwin/libkdecorations/kcommondecoration_p.h @@ -1,79 +1,79 @@ /* This file is part of the KDE project. Copyright (C) 2007 Lubos Lunak Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef KCOMMONDECORATION_P_H #define KCOMMONDECORATION_P_H #include "kdecoration.h" // // This header file is internal. I mean it. // class KCommonDecoration; class KCommonDecorationUnstable; class KDecorationBridge; class KDecorationFactory; // wrapper all functionality that needs reimplementing in KDecoration and forward it to KCommonDecoration class KCommonDecorationWrapper - : public KDecorationUnstable + : public KDecoration { Q_OBJECT public: KCommonDecorationWrapper(KCommonDecoration* deco, KDecorationBridge* bridge, KDecorationFactory* factory); virtual ~KCommonDecorationWrapper(); virtual void init(); virtual Position mousePosition(const QPoint& p) const; virtual void borders(int& left, int& right, int& top, int& bottom) const; virtual void resize(const QSize& s); virtual QSize minimumSize() const; virtual void activeChange(); virtual void captionChange(); virtual void iconChange(); virtual void maximizeChange(); virtual void desktopChange(); virtual void shadeChange(); virtual bool drawbound(const QRect& geom, bool clear); virtual bool windowDocked(Position side); virtual void reset(unsigned long changed); virtual void padding(int &left, int &right, int &top, int &bottom) const; void wrapSetAlphaEnabled(bool enabled); public Q_SLOTS: /** * This slot can be reimplemented to return the regions defined * by KDecorationDefines::Region. * * @since 4.10 */ QRegion region(KDecorationDefines::Region r); private: KCommonDecoration* decoration; }; #endif // KCOMMONDECORATION_P_H diff --git a/kwin/libkdecorations/kdecoration.cpp b/kwin/libkdecorations/kdecoration.cpp index 4fb4ccb9bd..7c07798d04 100644 --- a/kwin/libkdecorations/kdecoration.cpp +++ b/kwin/libkdecorations/kdecoration.cpp @@ -1,663 +1,654 @@ /***************************************************************** This file is part of the KDE project. Copyright (C) 2003 Lubos Lunak Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ******************************************************************/ #include "kdecoration.h" #include "kdecoration_p.h" #include #include #include #include #include #include "kdecorationfactory.h" #include "kdecorationbridge.h" /* Extending KDecoration: ====================== If KDecoration will ever need to be extended in a way that'd break binary compatibility (i.e. adding new virtual methods most probably), new class KDecoration2 should be inherited from KDecoration and those methods added there. Code that would depend on the new functionality could then dynamic_cast<> to KDecoration2 to check whether it is available and use it. KCommonDecoration would have to be extended the same way, adding KCommonDecoration2 inheriting KCommonDecoration and adding the new API matching KDecoration2. */ class KDecorationPrivate { public: KDecorationPrivate() : alphaEnabled(false) { } bool alphaEnabled; }; KDecorationOptions* KDecoration::options_; KDecoration::KDecoration(KDecorationBridge* bridge, KDecorationFactory* factory) : bridge_(bridge), w_(NULL), factory_(factory), d(new KDecorationPrivate()) { factory->addDecoration(this); } KDecoration::~KDecoration() { factory()->removeDecoration(this); delete w_; delete d; } const KDecorationOptions* KDecoration::options() { return options_; } void KDecoration::createMainWidget(Qt::WindowFlags flags) { // FRAME check flags? QWidget *w = new QWidget(initialParentWidget(), initialWFlags() | flags); w->setObjectName(QLatin1String("decoration widget")); w->setAttribute(Qt::WA_PaintOnScreen); if (options()->showTooltips()) w->setAttribute(Qt::WA_AlwaysShowToolTips); setMainWidget(w); } void KDecoration::setMainWidget(QWidget* w) { assert(w_ == NULL); w_ = w; w->setMouseTracking(true); widget()->resize(geometry().size()); } QWidget* KDecoration::initialParentWidget() const { return bridge_->initialParentWidget(); } Qt::WindowFlags KDecoration::initialWFlags() const { return bridge_->initialWFlags(); } bool KDecoration::isActive() const { return bridge_->isActive(); } bool KDecoration::isCloseable() const { return bridge_->isCloseable(); } bool KDecoration::isMaximizable() const { return bridge_->isMaximizable(); } KDecoration::MaximizeMode KDecoration::maximizeMode() const { return bridge_->maximizeMode(); } KDecoration::QuickTileMode KDecoration::quickTileMode() const { return bridge_->quickTileMode(); } bool KDecoration::isMinimizable() const { return bridge_->isMinimizable(); } bool KDecoration::providesContextHelp() const { return bridge_->providesContextHelp(); } int KDecoration::desktop() const { return bridge_->desktop(); } bool KDecoration::isModal() const { return bridge_->isModal(); } bool KDecoration::isShadeable() const { return bridge_->isShadeable(); } bool KDecoration::isShade() const { return bridge_->isShade(); } bool KDecoration::isSetShade() const { return bridge_->isSetShade(); } bool KDecoration::keepAbove() const { return bridge_->keepAbove(); } bool KDecoration::keepBelow() const { return bridge_->keepBelow(); } bool KDecoration::isMovable() const { return bridge_->isMovable(); } bool KDecoration::isResizable() const { return bridge_->isResizable(); } NET::WindowType KDecoration::windowType(unsigned long supported_types) const { // this one is also duplicated in KDecorationFactory return bridge_->windowType(supported_types); } QIcon KDecoration::icon() const { return bridge_->icon(); } QString KDecoration::caption() const { return bridge_->caption(); } void KDecoration::processMousePressEvent(QMouseEvent* e) { return bridge_->processMousePressEvent(e); } void KDecoration::showWindowMenu(const QRect &pos) { bridge_->showWindowMenu(pos); } void KDecoration::showWindowMenu(QPoint pos) { bridge_->showWindowMenu(pos); } void KDecoration::showApplicationMenu(const QPoint &p) { bridge_->showApplicationMenu(p); } bool KDecoration::menuAvailable() const { return bridge_->menuAvailable(); } void KDecoration::performWindowOperation(WindowOperation op) { bridge_->performWindowOperation(op); } void KDecoration::setMask(const QRegion& reg, int mode) { bridge_->setMask(reg, mode); } void KDecoration::clearMask() { bridge_->setMask(QRegion(), 0); } bool KDecoration::isPreview() const { return bridge_->isPreview(); } QRect KDecoration::geometry() const { return bridge_->geometry(); } QRect KDecoration::iconGeometry() const { return bridge_->iconGeometry(); } QRegion KDecoration::unobscuredRegion(const QRegion& r) const { return bridge_->unobscuredRegion(r); } WId KDecoration::windowId() const { return bridge_->windowId(); } void KDecoration::closeWindow() { bridge_->closeWindow(); } void KDecoration::maximize(Qt::MouseButtons button) { performWindowOperation(options()->operationMaxButtonClick(button)); } void KDecoration::maximize(MaximizeMode mode) { bridge_->maximize(mode); } void KDecoration::minimize() { bridge_->minimize(); } void KDecoration::showContextHelp() { bridge_->showContextHelp(); } void KDecoration::setDesktop(int desktop) { bridge_->setDesktop(desktop); } void KDecoration::toggleOnAllDesktops() { if (isOnAllDesktops()) setDesktop(bridge_->currentDesktop()); else setDesktop(NET::OnAllDesktops); } void KDecoration::titlebarDblClickOperation() { bridge_->titlebarDblClickOperation(); } void KDecoration::titlebarMouseWheelOperation(int delta) { bridge_->titlebarMouseWheelOperation(delta); } void KDecoration::setShade(bool set) { bridge_->setShade(set); } void KDecoration::setKeepAbove(bool set) { bridge_->setKeepAbove(set); } void KDecoration::setKeepBelow(bool set) { bridge_->setKeepBelow(set); } void KDecoration::emitKeepAboveChanged(bool above) { keepAboveChanged(above); } void KDecoration::emitKeepBelowChanged(bool below) { keepBelowChanged(below); } bool KDecoration::windowDocked(Position) { return false; } void KDecoration::reset(unsigned long) { } void KDecoration::grabXServer() { bridge_->grabXServer(true); } void KDecoration::ungrabXServer() { bridge_->grabXServer(false); } KDecoration::Position KDecoration::mousePosition(const QPoint& p) const { const int range = 16; int bleft, bright, btop, bbottom; borders(bleft, bright, btop, bbottom); btop = qMin(btop, 4); // otherwise whole titlebar would have resize cursor Position m = PositionCenter; if ((p.x() > bleft && p.x() < widget()->width() - bright) && (p.y() > btop && p.y() < widget()->height() - bbottom)) return PositionCenter; if (p.y() <= qMax(range, btop) && p.x() <= qMax(range, bleft)) m = PositionTopLeft; else if (p.y() >= widget()->height() - qMax(range, bbottom) && p.x() >= widget()->width() - qMax(range, bright)) m = PositionBottomRight; else if (p.y() >= widget()->height() - qMax(range, bbottom) && p.x() <= qMax(range, bleft)) m = PositionBottomLeft; else if (p.y() <= qMax(range, btop) && p.x() >= widget()->width() - qMax(range, bright)) m = PositionTopRight; else if (p.y() <= btop) m = PositionTop; else if (p.y() >= widget()->height() - bbottom) m = PositionBottom; else if (p.x() <= bleft) m = PositionLeft; else if (p.x() >= widget()->width() - bright) m = PositionRight; else m = PositionCenter; return m; } QRect KDecoration::transparentRect() const { return bridge_->transparentRect(); } void KDecoration::setAlphaEnabled(bool enabled) { if (d->alphaEnabled == enabled) { return; } d->alphaEnabled = enabled; emit alphaEnabledChanged(enabled); } bool KDecoration::isAlphaEnabled() const { return d->alphaEnabled; } -KDecorationUnstable::KDecorationUnstable(KDecorationBridge* bridge, KDecorationFactory* factory) - : KDecoration(bridge, factory) -{ -} - -KDecorationUnstable::~KDecorationUnstable() -{ -} - -bool KDecorationUnstable::compositingActive() const +bool KDecoration::compositingActive() const { return bridge_->compositingActive(); } -void KDecorationUnstable::padding(int &left, int &right, int &top, int &bottom) const +void KDecoration::padding(int &left, int &right, int &top, int &bottom) const { left = right = top = bottom = 0; } //BEGIN Window tabbing -int KDecorationUnstable::tabCount() const +int KDecoration::tabCount() const { return bridge_->tabCount(); } -long KDecorationUnstable::tabId(int idx) const +long KDecoration::tabId(int idx) const { return bridge_->tabId(idx); } -QString KDecorationUnstable::caption(int idx) const +QString KDecoration::caption(int idx) const { return bridge_->caption(idx); } -QIcon KDecorationUnstable::icon(int idx) const +QIcon KDecoration::icon(int idx) const { return bridge_->icon(idx); } -long KDecorationUnstable::currentTabId() const +long KDecoration::currentTabId() const { return bridge_->currentTabId(); } -void KDecorationUnstable::setCurrentTab(long id) +void KDecoration::setCurrentTab(long id) { bridge_->setCurrentTab(id); } -void KDecorationUnstable::tab_A_before_B(long A, long B) +void KDecoration::tab_A_before_B(long A, long B) { bridge_->tab_A_before_B(A, B); } -void KDecorationUnstable::tab_A_behind_B(long A, long B) +void KDecoration::tab_A_behind_B(long A, long B) { bridge_->tab_A_behind_B(A, B); } -void KDecorationUnstable::untab(long id, const QRect& newGeom) +void KDecoration::untab(long id, const QRect& newGeom) { bridge_->untab(id, newGeom); } -void KDecorationUnstable::closeTab(long id) +void KDecoration::closeTab(long id) { bridge_->closeTab(id); } -void KDecorationUnstable::closeTabGroup() +void KDecoration::closeTabGroup() { bridge_->closeTabGroup(); } -void KDecorationUnstable::showWindowMenu(const QPoint &pos, long id) +void KDecoration::showWindowMenu(const QPoint &pos, long id) { bridge_->showWindowMenu(pos, id); } //END tabbing -KDecoration::WindowOperation KDecorationUnstable::buttonToWindowOperation(Qt::MouseButtons button) +KDecoration::WindowOperation KDecoration::buttonToWindowOperation(Qt::MouseButtons button) { return bridge_->buttonToWindowOperation(button); } QRegion KDecoration::region(KDecorationDefines::Region) { return QRegion(); } KDecorationDefines::Position KDecoration::titlebarPosition() { return PositionTop; } QString KDecorationDefines::tabDragMimeType() { return QStringLiteral("text/ClientGroupItem"); } KDecorationOptions::KDecorationOptions() : d(new KDecorationOptionsPrivate) { assert(KDecoration::options_ == NULL); KDecoration::options_ = this; } KDecorationOptions::~KDecorationOptions() { assert(KDecoration::options_ == this); KDecoration::options_ = NULL; delete d; } QColor KDecorationOptions::color(ColorType type, bool active) const { return(d->colors[type + (active ? 0 : NUM_COLORS)]); } QFont KDecorationOptions::font(bool active, bool small) const { if (small) return(active ? d->activeFontSmall : d->inactiveFontSmall); else return(active ? d->activeFont : d->inactiveFont); } QPalette KDecorationOptions::palette(ColorType type, bool active) const { int idx = type + (active ? 0 : NUM_COLORS); if (d->pal[idx]) return(*d->pal[idx]); // TODO: Why construct the palette this way? Is it worth porting to Qt4? //d->pal[idx] = new QPalette(Qt::black, d->colors[idx], d->colors[idx].light(150), // d->colors[idx].dark(), d->colors[idx].dark(120), // Qt::black, QApplication::palette().active(). // base()); d->pal[idx] = new QPalette(d->colors[idx]); return(*d->pal[idx]); } unsigned long KDecorationOptions::updateSettings(KConfig* config) { return d->updateSettings(config); } bool KDecorationOptions::customButtonPositions() const { return d->custom_button_positions; } QString KDecorationOptions::titleButtonsLeft() const { return d->title_buttons_left; } QString KDecorationOptions::defaultTitleButtonsLeft() { return QStringLiteral("MS"); } QString KDecorationOptions::titleButtonsRight() const { return d->title_buttons_right; } QString KDecorationOptions::defaultTitleButtonsRight() { return QStringLiteral("HIAX"); } bool KDecorationOptions::showTooltips() const { return d->show_tooltips; } KDecorationOptions::BorderSize KDecorationOptions::preferredBorderSize(KDecorationFactory* factory) const { assert(factory != NULL); if (d->cached_border_size == BordersCount) // invalid d->cached_border_size = d->findPreferredBorderSize(d->border_size, factory->borderSizes()); return d->cached_border_size; } KDecorationDefines::WindowOperation KDecorationOptions::operationMaxButtonClick(Qt::MouseButtons button) const { return button == Qt::RightButton ? d->opMaxButtonRightClick : button == Qt::MidButton ? d->opMaxButtonMiddleClick : d->opMaxButtonLeftClick; } void KDecorationOptions::setOpMaxButtonLeftClick(WindowOperation op) { d->opMaxButtonLeftClick = op; } void KDecorationOptions::setOpMaxButtonRightClick(WindowOperation op) { d->opMaxButtonRightClick = op; } void KDecorationOptions::setOpMaxButtonMiddleClick(WindowOperation op) { d->opMaxButtonMiddleClick = op; } void KDecorationOptions::setBorderSize(BorderSize bs) { d->border_size = bs; d->cached_border_size = BordersCount; // invalid } void KDecorationOptions::setCustomButtonPositions(bool b) { d->custom_button_positions = b; } void KDecorationOptions::setTitleButtonsLeft(const QString& b) { d->title_buttons_left = b; } void KDecorationOptions::setTitleButtonsRight(const QString& b) { d->title_buttons_right = b; } extern "C" { int decoration_bridge_version() { return KWIN_DECORATION_BRIDGE_API_VERSION; } } #include "kdecoration.moc" diff --git a/kwin/libkdecorations/kdecoration.h b/kwin/libkdecorations/kdecoration.h index d208852cd0..29ee3ddd4c 100644 --- a/kwin/libkdecorations/kdecoration.h +++ b/kwin/libkdecorations/kdecoration.h @@ -1,1147 +1,1128 @@ /***************************************************************** This file is part of the KDE project. Copyright (C) 2003 Lubos Lunak Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ******************************************************************/ #ifndef KDECORATION_H #define KDECORATION_H #include #include #include #include #include #include #include #define KWIN_DECORATION_API_VERSION 1 /** * Defines the class to be used for decoration factory. * The class must be namespace complete. * E.g. KWIN_EFFECT( Oxygen::Factory ) **/ #define KWIN_DECORATION( classname ) \ extern "C" { \ KWIN_EXPORT KDecorationFactory* create_factory() { return new classname(); } \ KWIN_EXPORT int decoration_version() { return KWIN_DECORATION_API_VERSION; } \ } #define KWIN_DECORATION_BRIDGE_API_VERSION 1 extern "C" { int decoration_bridge_version(); } class KConfig; /** @defgroup kdecoration KWin decorations library */ /** @addtogroup kdecoration */ /** @{ */ class KDecorationOptionsPrivate; class KDecorationBridge; class KDecorationPrivate; class KDecorationFactory; /** * This class provides a namespace for all decoration related classes. * All shared types are defined here. */ class KWIN_EXPORT KDecorationDefines { public: /** * These values represent positions inside an area */ enum Position { // without prefix, they'd conflict with Qt::TopLeftCorner etc. :( PositionCenter = 0x00, PositionLeft = 0x01, PositionRight = 0x02, PositionTop = 0x04, PositionBottom = 0x08, PositionTopLeft = PositionLeft | PositionTop, PositionTopRight = PositionRight | PositionTop, PositionBottomLeft = PositionLeft | PositionBottom, PositionBottomRight = PositionRight | PositionBottom }; /** * Maximize mode. These values specify how a window is maximized. */ // these values are written to session files, don't change the order enum MaximizeMode { MaximizeRestore = 0, ///< The window is not maximized in any direction. MaximizeVertical = 1, ///< The window is maximized vertically. MaximizeHorizontal = 2, ///< The window is maximized horizontally. /// Equal to @p MaximizeVertical | @p MaximizeHorizontal MaximizeFull = MaximizeVertical | MaximizeHorizontal }; enum QuickTileFlag { QuickTileNone = 0, QuickTileLeft = 1, QuickTileRight = 1<<1, QuickTileTop = 1<<2, QuickTileBottom = 1<<3, QuickTileHorizontal = QuickTileLeft|QuickTileRight, QuickTileVertical = QuickTileTop|QuickTileBottom, QuickTileMaximize = QuickTileLeft|QuickTileRight|QuickTileTop|QuickTileBottom }; Q_DECLARE_FLAGS(QuickTileMode, QuickTileFlag) 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, }; /** * Basic color types that should be recognized by all decoration styles. * Decorations are not required to implement all the colors, but for the ones that * are implemented the color setting for them should be obeyed. */ enum ColorType { ColorTitleBar, ///< The color for the titlebar ColorTitleBlend, ///< The blend color for the titlebar ColorFont, ///< The titlebar text color ColorButtonBg, ///< The color to use for the titlebar buttons ColorFrame, ///< The color for the window frame (border) ColorHandle, ///< The color for the resize handle NUM_COLORS ///< @internal This value may change, do not use }; /** * These flags specify which settings changed when rereading settings. * Each setting in class KDecorationOptions specifies its matching flag. */ enum { SettingDecoration = 1 << 0, ///< The decoration was changed SettingColors = 1 << 1, ///< The color palette was changed SettingFont = 1 << 2, ///< The titlebar font was changed SettingButtons = 1 << 3, ///< The button layout was changed SettingTooltips = 1 << 4, ///< The tooltip setting was changed SettingBorder = 1 << 5, ///< The border size setting was changed SettingCompositing = 1 << 6 ///< Compositing settings was changed }; /** * Border size. KDecorationOptions::preferredBorderSize() returns * one of these values. */ enum BorderSize { BorderTiny, ///< Minimal borders BorderNormal, ///< Standard size borders, the default setting BorderLarge, ///< Larger borders BorderVeryLarge, ///< Very large borders BorderHuge, ///< Huge borders BorderVeryHuge, ///< Very huge borders BorderOversized, ///< Oversized borders BorderNoSides, ///< No borders on sides @since 4.11 BorderNone, ///< No borders except title @since 4.11 BordersCount ///< @internal }; /** * Used to find out which features the decoration supports. * @see KDecorationFactory::supports() */ enum Ability { // announce AbilityAnnounceButtons = 0, ///< decoration supports AbilityButton* values (always use) // buttons AbilityButtonMenu = 1000, ///< decoration supports the window menu button AbilityButtonOnAllDesktops = 1001, ///< decoration supports the on all desktops button AbilityButtonSpacer = 1002, ///< decoration supports inserting spacers between buttons AbilityButtonHelp = 1003, ///< decoration supports what's this help button AbilityButtonMinimize = 1004, ///< decoration supports a minimize button AbilityButtonMaximize = 1005, ///< decoration supports a maximize button AbilityButtonClose = 1006, ///< decoration supports a close button AbilityButtonAboveOthers = 1007, ///< decoration supports an above button AbilityButtonBelowOthers = 1008, ///< decoration supports a below button AbilityButtonShade = 1009, ///< decoration supports a shade button AbilityButtonResize = 1010, ///< decoration supports a resize button AbilityButtonApplicationMenu = 1011, ///< decoration supports the application menu button // compositing AbilityProvidesShadow = 3000, ///< The decoration draws its own shadows. /// @since 4.3 AbilityUsesAlphaChannel = 3001, ///< The decoration isn't clipped to the mask when compositing is enabled. /// The mask is still used to define the input region and the blurred /// region, when the blur plugin is enabled. /// @since 4.3 AbilityExtendIntoClientArea = 3002, ///< The decoration respects transparentRect() /// @since 4.4 AbilityUsesBlurBehind = 3003, ///< The decoration wants the background to be blurred, when the blur plugin is enabled. /// @since 4.6 AbilityAnnounceAlphaChannel = 4004, ///< The decoration can tell whether it currently uses an alpha channel or not. Requires AbilityUsesAlphaChannel. /// @since 4.10 // Tabbing AbilityTabbing = 4000, ///< The decoration supports tabbing // TODO colors for individual button types ABILITY_DUMMY = 10000000, }; enum Requirement { REQUIREMENT_DUMMY = 1000000 }; /** * Regions that can be returned by KDecorationUnstable::region(). */ enum Region { /** * This is an invisible input region that can be used to expand the * borders by an invisible amount, both inside and outside the * decoration. The intended use case is to provide an active border * area that's larger than the visible border. * * The mousePosition() implementation must return correct values * for the pixels inside this region. * * Note that mouse events that occur within this region are not * forwarded to the decoration. This may change in the future. * * @since 4.8 */ ExtendedBorderRegion }; /** * Returns the mimeType used to drag and drop clientGroupItems */ static QString tabDragMimeType(); }; class KDecorationProvides : public KDecorationDefines { public: virtual ~KDecorationProvides() {} virtual bool provides(Requirement req) = 0; }; /** * This class holds various configuration settings for the decoration. * It is accessible from the decorations either as KDecoration::options() * or KDecorationFactory::options(). */ class KWIN_EXPORT KDecorationOptions : public KDecorationDefines { public: KDecorationOptions(); virtual ~KDecorationOptions(); /** * Call to update settings when the config changes. Return value is * a combination of Setting* (SettingColors, etc.) that have changed. * @since 4.0.1 */ unsigned long updateSettings(KConfig* config); /** * Returns the color that should be used for the given part of the decoration. * The changed flags for this setting is SettingColors. * * @param type The requested color type. * @param active Whether the color should be for active or inactive windows. */ QColor color(ColorType type, bool active = true) const; /** * Returns a palette using the given decoration color as the background. * The changed flags for this setting is SettingColors. * * @param type The requested color type. * @param active Whether to return the color for active or inactive windows. */ QPalette palette(ColorType type, bool active = true) const; /** * Returns the active or inactive decoration font. * The changed flags for this setting is SettingFont. * * @param active Whether to return the color for active or inactive windows. * @param small If @a true, returns a font that's suitable for tool windows. */ QFont font(bool active = true, bool small = false) const; /** * Returns @a true if the style should use custom button positions * The changed flags for this setting is SettingButtons. * * @see titleButtonsLeft * @see titleButtonsRight */ bool customButtonPositions() const; /** * If customButtonPositions() returns true, titleButtonsLeft * returns which buttons should be on the left side of the titlebar from left * to right. Characters in the returned string have this meaning : * @li 'N' application menu button * @li 'M' window menu button * @li 'S' on_all_desktops button * @li 'H' quickhelp button * @li 'I' minimize ( iconify ) button * @li 'A' maximize button * @li 'X' close button * @li 'F' keep_above_others button * @li 'B' keep_below_others button * @li 'L' shade button * @li 'R' resize button * @li '_' spacer * * The default ( which is also returned if customButtonPositions returns false ) * is "MS". * Unknown buttons in the returned string must be ignored. * The changed flags for this setting is SettingButtons. */ QString titleButtonsLeft() const; /** * Returns the default left button sequence */ static QString defaultTitleButtonsLeft(); /** * If customButtonPositions() returns true, titleButtonsRight * returns which buttons should be on the right side of the titlebar from left * to right. Characters in the return string have the same meaning like * in titleButtonsLeft(). * * The default ( which is also returned if customButtonPositions returns false ) * is "HIA__X". * Unknown buttons in the returned string must be ignored. * The changed flags for this setting is SettingButtons. */ QString titleButtonsRight() const; /** * Returns the default right button sequence. */ static QString defaultTitleButtonsRight(); /** * @returns true if the style should use tooltips for window buttons * The changed flags for this setting is SettingTooltips. */ bool showTooltips() const; /** * The preferred border size selected by the user, e.g. for accessibility * reasons, or when using high resolution displays. It's up to the decoration * to decide which borders or if any borders at all will obey this setting. * It is guaranteed that the returned value will be one of those * returned by KDecorationFactory::borderSizes(), so if that one hasn't been * reimplemented, BorderNormal is always returned. * The changed flags for this setting is SettingBorder. * @param factory the decoration factory used */ BorderSize preferredBorderSize(KDecorationFactory* factory) const; /** * @internal */ WindowOperation operationMaxButtonClick(Qt::MouseButtons button) const; /** * @internal */ virtual unsigned long updateSettings() = 0; // returns SettingXYZ mask protected: /** @internal */ void setOpMaxButtonLeftClick(WindowOperation op); /** @internal */ void setOpMaxButtonRightClick(WindowOperation op); /** @internal */ void setOpMaxButtonMiddleClick(WindowOperation op); /** @internal */ void setBorderSize(BorderSize bs); /** @internal */ void setCustomButtonPositions(bool b); /** @internal */ void setTitleButtonsLeft(const QString& b); /** @internal */ void setTitleButtonsRight(const QString& b); private: /** * @internal */ KDecorationOptionsPrivate* d; }; /** * This is the base class for a decoration object. It provides functions * that give various information about the decorated window, and also * provides pure virtual functions for controlling the decoration that * every decoration should implement. */ class KWIN_EXPORT KDecoration : public QObject, public KDecorationDefines { Q_OBJECT public: /** * Constructs a KDecoration object. Both the arguments are passed from * KDecorationFactory. Note that the initialization code of the decoration * should be done in the init() method. */ KDecoration(KDecorationBridge* bridge, KDecorationFactory* factory); /** * Destroys the KDecoration. */ virtual ~KDecoration(); // requests from decoration /** * Returns the KDecorationOptions object, which is used to access * configuration settings for the decoration. */ static const KDecorationOptions* options(); /** * Returns @a true if the decorated window is currently active. */ bool isActive() const; /** * Returns @a true if the decoration window can be closed by the user. */ bool isCloseable() const; /** * Returns @a true if the decorated window can be maximized. */ bool isMaximizable() const; /** * Returns the current maximization mode of the decorated window. * Note that only fully maximized windows should be treated * as "maximized" (e.g. if the maximize button has only two states). */ MaximizeMode maximizeMode() const; /** * Returns @a true if the decorated window can be minimized by the user. */ /** * Returns the current quicktiling mode of the decorated window. * (window is places into one of the corners or edges) */ QuickTileMode quickTileMode() const; bool isMinimizable() const; /** * Return @a true if the decorated window can show context help * (i.e. the decoration should provide the context help button). */ bool providesContextHelp() const; /** * Returns the number of the virtual desktop the decorated window * is currently on (including NET::OnAllDesktops for being on all * desktops). */ int desktop() const; /** * Convenience function that returns @a true if the window is on all * virtual desktops. */ bool isOnAllDesktops() const; // convenience /** * Returns @a true if the decoration window is modal (usually a modal dialog). */ bool isModal() const; /** * Returns @a true if the decorated window can be shaded. */ bool isShadeable() const; /** * Returns @a true if the decorated window is currently shaded. * If the window is e.g. hover unshaded, it's not considered to be shaded. * This function should not be used for the shade titlebar button, use * @ref isSetShade() instead. * * @see isSetShade */ bool isShade() const; /** * Returns @a true if the decorated window was set to be shaded. This function * returns also true if the window is e.g. hover unshaded, so it doesn't * always correspond to the actual window state. * * @see isShade */ bool isSetShade() const; /** * Returns @a true if the decorated window should be kept above other windows. */ bool keepAbove() const; /** * Returns @a true if the decorated window should be kept below other windows. */ bool keepBelow() const; /** * Returns @a true if the decorated window can be moved by the user. */ bool isMovable() const; /** * Returns @a true if the decorated window can be resized by the user. */ bool isResizable() const; /** * This function returns the window type of the decorated window. * The argument to this function is a mask of all window types * the decoration knows about (as the list of valid window types * is extended over time, and fallback types are specified in order * to support older code). For a description of all window types, * see the definition of the NET::WindowType type. Note that * some window types never have decorated windows. * * An example of usage: * @code * const unsigned long supported_types = NET::NormalMask | NET::DesktopMask * | NET::DockMask | NET::ToolbarMask | NET::MenuMask | NET::DialogMask * | NET::OverrideMask | NET::TopMenuMask | NET::UtilityMask | NET::SplashMask; * * NET::WindowType type = windowType( supported_types ); * * if ( type == NET::Utility || type == NET::Menu || type == NET::Toolbar ) * // ... use smaller decorations for tool window types * else * // ... use normal decorations * @endcode */ NET::WindowType windowType(unsigned long supported_types) const; /** * Returns an icon set with the decorated window's icon. */ QIcon icon() const; /** * Returns the decorated window's caption that should be shown in the titlebar. */ QString caption() const; /** * This function invokes the window operations menu. * \param pos specifies the place on the screen where the menu should * show up. The menu pops up at the bottom-left corner of the specified * rectangle, unless there is no space, in which case the menu is * displayed above the rectangle. * * \note Decorations that enable a double-click operation for the menu * button must ensure to call \a showWindowMenu() with the \a pos * rectangle set to the menu button geometry. * IMPORTANT: As a result of this function, the decoration object that * called it may be destroyed after the function returns. This means * that the decoration object must either return immediately after * calling showWindowMenu(), or it must use * KDecorationFactory::exists() to check it's still valid. For example, * the code handling clicks on the menu button should look similarly * like this: * * \code * KDecorationFactory* f = factory(); // needs to be saved before * showWindowMenu( button[MenuButton]->mapToGlobal( menuPoint )); * if ( !f->exists( this )) // destroyed, return immediately * return; * button[MenuButton]->setDown(false); * \endcode */ void showWindowMenu(const QRect &pos); /** * Overloaded version of the above. */ void showWindowMenu(QPoint pos); /** * show application menu at p */ void showApplicationMenu(const QPoint& p); /** * Returns @a true if menu available for client */ bool menuAvailable() const; /** * This function performs the given window operation. This function may destroy * the current decoration object, just like showWindowMenu(). */ void performWindowOperation(WindowOperation op); /** * If the decoration is non-rectangular, this function needs to be called * to set the shape of the decoration. * * @param reg The shape of the decoration. * @param mode The X11 values Unsorted, YSorted, YXSorted and YXBanded that specify * the sorting of the rectangles, default value is Unsorted. */ void setMask(const QRegion& reg, int mode = 0); /** * This convenience function resets the shape mask. */ void clearMask(); // convenience /** * If this function returns @a true, the decorated window is used as a preview * e.g. in the configuration module. In such case, the decoration can e.g. * show some information in the window area. */ bool isPreview() const; /** * Returns the geometry of the decoration. */ QRect geometry() const; /** * Returns the icon geometry for the window, i.e. the geometry of the taskbar * entry. This is used mainly for window minimize animations. Note that * the geometry may be null. */ QRect iconGeometry() const; /** * Returns the intersection of the given region with the region left * unobscured by the windows stacked above the current one. You can use * this function to, for example, try to keep the titlebar visible if * there is a hole available. The region returned is in the coordinate * space of the decoration. * @param r The region you want to check for holes */ QRegion unobscuredRegion(const QRegion& r) const; /** * Returns the handle of the window that is being decorated. It is possible * the returned value will be 0. * IMPORTANT: This function is meant for special purposes, and it * usually should not be used. The main purpose is finding out additional * information about the window's state. Also note that different kinds * of windows are decorated: Toplevel windows managed by the window manager, * test window in the window manager decoration module, and possibly also * other cases. * Careless abuse of this function will usually sooner or later lead * to problems. */ WId windowId() const; /** * Convenience function that returns the width of the decoration. */ int width() const; // convenience /** * Convenience function that returns the height of the decoration. */ int height() const; // convenience /** * Returns the rectangle within the window frame that should be transparent. * Usually this rectangle is the same as the client area, and it is never * larger. * * If the client has requested that the window frame is extended into the * client area, this rectangle is smaller than the client area. * * If the window frame should cover the whole client area, a null rectangle * is returned. * * @since 4.4 */ QRect transparentRect() const; /** * This function is the default handler for mouse events. All mouse events * that are not handled by the decoration itself should be passed to it * in order to make work operations like window resizing by dragging borders etc. */ void processMousePressEvent(QMouseEvent* e); /** * Whether the alpha channel is currently enabled. The value of this property is * only relevant in case the decoration provides the AbilityAnnounceAlphaChannel. * * The compositor can make use of this information to optimize the rendering of the * decoration. * * The default value of this property is @c false. That means if a decoration wants to make * use alpha channel it has to call setAlphaEnabled with @c true. * * @see setAlphaEnabled * @see alphaEnabledChanged * @since 4.10 **/ bool isAlphaEnabled() const; // requests to decoration /** * This function is called immediately after the decoration object is created. * Due to some technical reasons, initialization should be done here * instead of in the constructor. */ virtual void init() = 0; // called once right after created /** * This function should return mouse cursor position in the decoration. * Positions at the edge will result in window resizing with mouse button * pressed, center position will result in moving. */ virtual Position mousePosition(const QPoint& p) const = 0; /** * This function should return the distance from each window side to the inner * window. The sizes may depend on the state of the decorated window, such as * whether it's shaded. Decorations often turn off their bottom border when the * window is shaded, and turn off their left/right/bottom borders when * the window is maximized and moving and resizing of maximized windows is disabled. * This function mustn't do any repaints or resizes. Also, if the sizes returned * by this function don't match the real values, this may result in drawing errors * or other problems. * * @see KDecorationOptions::moveResizeMaximizedWindows() */ // mustn't do any repaints, resizes or anything like that virtual void borders(int& left, int& right, int& top, int& bottom) const = 0; /** * This method is called by kwin when the style should resize the decoration window. * The usual implementation is to resize the main widget of the decoration to the * given size. * * @param s Specifies the new size of the decoration window. */ virtual void resize(const QSize& s) = 0; /** * This function should return the minimum required size for the decoration. * Note that the returned size shouldn't be too large, because it will be * used to keep the decorated window at least as large. */ virtual QSize minimumSize() const = 0; public Q_SLOTS: /** * This function is called whenever the window either becomes or stops being active. * Use isActive() to find out the current state. */ virtual void activeChange() = 0; /** * This function is called whenever the caption changes. Use caption() to get it. */ virtual void captionChange() = 0; /** * This function is called whenever the window icon changes. Use icon() to get it. */ virtual void iconChange() = 0; /** * This function is called whenever the maximalization state of the window changes. * Use maximizeMode() to get the current state. */ virtual void maximizeChange() = 0; /** * This function is called whenever the desktop for the window changes. Use * desktop() or isOnAllDesktops() to find out the current desktop * on which the window is. */ virtual void desktopChange() = 0; /** * This function is called whenever the window is shaded or unshaded. Use * isShade() to get the current state. */ virtual void shadeChange() = 0; Q_SIGNALS: /** * This signal is emitted whenever the window's keep-above state changes. */ void keepAboveChanged(bool); /** * This signal is emitted whenever the window's keep-below state changes. */ void keepBelowChanged(bool); /** * This signal is emitted whenever application menu is closed * Application menu button may need to be modified on this signal */ void menuHidden(); /** * This signal is emitted whenever application want to show it menu */ void showRequest(); /** * This signal is emitted whenever application menu becomes available */ void appMenuAvailable(); /** * This signal is emitted whenever application menu becomes unavailable */ void appMenuUnavailable(); /** * This signal is emitted whenever the decoration changes it's alpha enabled * change. Only relevant in case the decoration provides AbilityAnnounceAlphaChannel. * * @param enabled The new state of alpha channel usage * @see setAlphaEnabled * @see isAlphaEnabled * @since 4.10 **/ void alphaEnabledChanged(bool enabled); public: /** * @internal Reserved. */ // TODO position will need also values for top+left+bottom etc. docking ? virtual bool windowDocked(Position side); /** * This function is called to reset the decoration on settings changes. * It is usually invoked by calling KDecorationFactory::resetDecorations(). * * @param changed Specifies which settings were changed, given by the SettingXXX masks */ virtual void reset(unsigned long changed); + /** + * This function can return additional padding values that are added outside the + * borders of the window, and can be used by the decoration if it wants to paint + * outside the frame. + * + * The typical use case is for drawing a drop shadow or glowing effect around the window. + * + * The area outside the frame cannot receive input, and when compositing is disabled, + * painting is clipped to the mask, or the window frame if no mask is defined. + */ + virtual void padding(int &left, int &right, int &top, int &bottom) const; // special /** * This should be the first function called in init() to specify * the main widget of the decoration. The widget should be created * with parent specified by initialParentWidget() and flags * specified by initialWFlags(). */ void setMainWidget(QWidget*); /** * Convenience functions that creates and sets a main widget as necessary. * In such case, it's usually needed to install an event filter * on the main widget to receive important events on it. * * @param flags Additional widget flags for the main widget. Note that only * flags that affect widget drawing are allowed. Window type flags * like WX11BypassWM or WStyle_NoBorder are forbidden. */ void createMainWidget(Qt::WindowFlags flags = 0); /** * The parent widget that should be used for the main widget. */ QWidget* initialParentWidget() const; /** * The flags that should be used when creating the main widget. * It is possible to add more flags when creating the main widget, but only flags * that affect widget drawing are allowed. Window type flags like WX11BypassWM * or WStyle_NoBorder are forbidden. */ Qt::WindowFlags initialWFlags() const; /** * Returns the main widget for the decoration. */ QWidget* widget(); /** * Returns the main widget for the decoration. */ const QWidget* widget() const; /** * Returns the factory that created this decoration. */ KDecorationFactory* factory() const; /** * Performs X server grab. It is safe to call it several times in a row. */ void grabXServer(); /** * Ungrabs X server (if the number of ungrab attempts matches the number of grab attempts). */ void ungrabXServer(); + /** + * Returns @a true if compositing--and therefore ARGB--is enabled. + */ + bool compositingActive() const; + /** + * Determine which action the user has mapped \p button to. Useful for determining whether + * a button press was for window tab dragging or for displaying the client menu. + */ + WindowOperation buttonToWindowOperation(Qt::MouseButtons button); + + + // Window tabbing + + /** + * Returns whether or not this client group contains the active client. + */ + bool isInActiveTabGroup(); + /** + * Return the amount of tabs in this group + */ + int tabCount() const; + + /** + * Return the icon for the tab at index \p idx (\p idx must be smaller than tabCount()) + */ + QIcon icon(int idx) const; + + /** + * Return the caption for the tab at index \p idx (\p idx must be smaller than tabCount()) + */ + QString caption(int idx) const; + + /** + * Return the unique id for the tab at index \p idx (\p idx must be smaller than tabCount()) + */ + long tabId(int idx) const; + /** + * Returns the id of the currently active client in this group. + */ + long currentTabId() const; + /** + * Activate tab for the window with the id \p id. + */ + void setCurrentTab(long id); + + /** + * Entab windw with id \p A beFORE the window with the id \p B. + */ + virtual void tab_A_before_B(long A, long B); + /** + * Entab windw with id \p A beHIND the window with the id \p B. + */ + virtual void tab_A_behind_B(long A, long B); + /** + * Remove the window with the id \p id from its tabgroup and place it at \p newGeom + */ + virtual void untab(long id, const QRect& newGeom); + + /** + * Close the client with the id \p id. + */ + void closeTab(long id); + /** + * Close all windows in this group. + */ + void closeTabGroup(); + /** + * Display the right-click client menu belonging to the client at index \p index at the + * global coordinates specified by \p pos. + */ + void showWindowMenu(const QPoint& pos, long id); public: // invokables; runtime resolution /** * reimplement this invokable to signal the core where the titlebar is (usually PositionTop) */ Q_INVOKABLE KDecorationDefines::Position titlebarPosition(); public Q_SLOTS: // requests from decoration /** * This function can be called by the decoration to request * closing of the decorated window. Note that closing the window * also involves destroying the decoration. * IMPORTANT: This function may destroy the current decoration object, * just like showWindowMenu(). */ void closeWindow(); /** * Changes the maximize mode of the decorated window. This function should * be preferred to the other maximize() overload for reacting on clicks * on the maximize titlebar button. */ void maximize(Qt::MouseButtons button); /** * Set the maximize mode of the decorated window. * @param mode The maximization mode to be set. */ void maximize(MaximizeMode mode); /** * Minimize the decorated window. */ void minimize(); /** * Start showing context help in the window (i.e. the mouse will enter * the what's this mode). */ void showContextHelp(); /** * Moves the window to the given desktop. Use NET::OnAllDesktops for making * the window appear on all desktops. */ void setDesktop(int desktop); /** * This function toggles the on-all-desktops state of the decorated window. */ void toggleOnAllDesktops(); // convenience /** * This function performs the operation configured as titlebar double click * operation. */ void titlebarDblClickOperation(); /** * This function performs the operation configured as titlebar wheel mouse * operation. * @param delta the mouse wheel delta */ void titlebarMouseWheelOperation(int delta); /** * Shades or unshades the decorated window. * @param set Whether the window should be shaded */ void setShade(bool set); /** * Sets or reset keeping this window above others. * @param set Whether to keep the window above others */ void setKeepAbove(bool set); /** * Sets or reset keeping this window below others. * @param set Whether to keep the window below others */ void setKeepBelow(bool set); /** * @internal * TODO KF5: remove me */ void emitKeepAboveChanged(bool above); /** * @internal * TODO KF5: remove me */ void emitKeepBelowChanged(bool below); protected Q_SLOTS: /** * A decoration providing AbilityAnnounceAlphaChannel can use this method to enable/disable the * use of alpha channel. This is useful if for a normal window the decoration renders its own * shadows or round corners and thus needs alpha channel. But in maximized state the decoration * is fully opaque. By disabling the alpha channel the Compositor can optimize the rendering. * * @param enabled If @c true alpha channel is enabled, if @c false alpha channel is disabled * @see isAlphaEnabled * @see alphaEnabledChanged * @since 4.10 **/ void setAlphaEnabled(bool enabled); /** * This slot can be reimplemented to return the regions defined * by KDecorationDefines::Region. * * The default implementation always returns an empty region. * * @since 4.8 */ QRegion region(KDecorationDefines::Region r); private: KDecorationBridge* bridge_; QWidget* w_; KDecorationFactory* factory_; friend class KDecorationOptions; // for options_ - friend class KDecorationUnstable; // for bridge_ static KDecorationOptions* options_; KDecorationPrivate* d; }; -/** - * @warning THIS CLASS IS UNSTABLE! - */ -class KWIN_EXPORT KDecorationUnstable - : public KDecoration -{ - Q_OBJECT - -public: - KDecorationUnstable(KDecorationBridge* bridge, KDecorationFactory* factory); - virtual ~KDecorationUnstable(); - /** - * This function can return additional padding values that are added outside the - * borders of the window, and can be used by the decoration if it wants to paint - * outside the frame. - * - * The typical use case is for drawing a drop shadow or glowing effect around the window. - * - * The area outside the frame cannot receive input, and when compositing is disabled, - * painting is clipped to the mask, or the window frame if no mask is defined. - */ - virtual void padding(int &left, int &right, int &top, int &bottom) const; - /** - * Returns @a true if compositing--and therefore ARGB--is enabled. - */ - bool compositingActive() const; - - // Window tabbing - - /** - * Returns whether or not this client group contains the active client. - */ - bool isInActiveTabGroup(); - /** - * Return the amount of tabs in this group - */ - int tabCount() const; - - /** - * Return the icon for the tab at index \p idx (\p idx must be smaller than tabCount()) - */ - QIcon icon(int idx) const; - - /** - * Return the caption for the tab at index \p idx (\p idx must be smaller than tabCount()) - */ - QString caption(int idx) const; - - /** - * Return the unique id for the tab at index \p idx (\p idx must be smaller than tabCount()) - */ - long tabId(int idx) const; - /** - * Returns the id of the currently active client in this group. - */ - long currentTabId() const; - /** - * Activate tab for the window with the id \p id. - */ - void setCurrentTab(long id); - - /** - * Entab windw with id \p A beFORE the window with the id \p B. - */ - virtual void tab_A_before_B(long A, long B); - /** - * Entab windw with id \p A beHIND the window with the id \p B. - */ - virtual void tab_A_behind_B(long A, long B); - /** - * Remove the window with the id \p id from its tabgroup and place it at \p newGeom - */ - virtual void untab(long id, const QRect& newGeom); - - /** - * Close the client with the id \p id. - */ - void closeTab(long id); - /** - * Close all windows in this group. - */ - void closeTabGroup(); - /** - * Display the right-click client menu belonging to the client at index \p index at the - * global coordinates specified by \p pos. - */ - void showWindowMenu(const QPoint& pos, long id); - /** - * unshadow virtuals - */ - using KDecoration::caption; - using KDecoration::icon; - using KDecoration::showWindowMenu; - /** - * Determine which action the user has mapped \p button to. Useful for determining whether - * a button press was for window tab dragging or for displaying the client menu. - */ - WindowOperation buttonToWindowOperation(Qt::MouseButtons button); -}; - inline KDecorationDefines::MaximizeMode operator^(KDecorationDefines::MaximizeMode m1, KDecorationDefines::MaximizeMode m2) { return KDecorationDefines::MaximizeMode(int(m1) ^ int(m2)); } inline KDecorationDefines::MaximizeMode operator&(KDecorationDefines::MaximizeMode m1, KDecorationDefines::MaximizeMode m2) { return KDecorationDefines::MaximizeMode(int(m1) & int(m2)); } inline KDecorationDefines::MaximizeMode operator|(KDecorationDefines::MaximizeMode m1, KDecorationDefines::MaximizeMode m2) { return KDecorationDefines::MaximizeMode(int(m1) | int(m2)); } inline QWidget* KDecoration::widget() { return w_; } inline const QWidget* KDecoration::widget() const { return w_; } inline KDecorationFactory* KDecoration::factory() const { return factory_; } inline bool KDecoration::isOnAllDesktops() const { return desktop() == NET::OnAllDesktops; } inline int KDecoration::width() const { return geometry().width(); } inline int KDecoration::height() const { return geometry().height(); } /** @} */ Q_DECLARE_OPERATORS_FOR_FLAGS(KDecoration::QuickTileMode) Q_DECLARE_METATYPE(KDecorationDefines::WindowOperation) #endif