kwin/manage.cpp

775 lines
32 KiB
C++
Raw Normal View History

/********************************************************************
KWin - the KDE window manager
This file is part of the KDE project.
Copyright (C) 1999, 2000 Matthias Ettrich <ettrich@kde.org>
Copyright (C) 2003 Lubos Lunak <l.lunak@kde.org>
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 <http://www.gnu.org/licenses/>.
*********************************************************************/
// This file contains things relevant to handling incoming events.
#include "client.h"
#include <kstartupinfo.h>
#ifdef KWIN_BUILD_ACTIVITIES
#include "activities.h"
#endif
#include "cursor.h"
#include <QX11Info>
#include "rules.h"
#include "group.h"
#include "netinfo.h"
#include "screens.h"
#include "workspace.h"
#include "xcbutils.h"
#include <xcb/shape.h>
namespace KWin
{
/**
* Manages the clients. This means handling the very first maprequest:
* reparenting, initial geometry, initial state, placement, etc.
* Returns false if KWin is not going to manage this window.
*/
bool Client::manage(xcb_window_t w, bool isMapped)
2011-01-30 14:34:42 +00:00
{
StackingUpdatesBlocker stacking_blocker(workspace());
grabXServer();
Xcb::WindowAttributes attr(w);
Xcb::WindowGeometry windowGeometry(w);
if (attr.isNull() || windowGeometry.isNull()) {
ungrabXServer();
return false;
2011-01-30 14:34:42 +00:00
}
// From this place on, manage() must not return false
block_geometry_updates = 1;
pending_geometry_update = PendingGeometryForced; // Force update when finishing with geometry changes
embedClient(w, attr->visual, attr->colormap, windowGeometry->depth);
m_visual = attr->visual;
bit_depth = windowGeometry->depth;
// SELI TODO: Order all these things in some sane manner
bool init_minimize = false;
2011-01-30 14:34:42 +00:00
XWMHints* hints = XGetWMHints(display(), w);
if (hints && (hints->flags & StateHint) && hints->initial_state == IconicState)
init_minimize = true;
2011-01-30 14:34:42 +00:00
if (hints)
XFree(hints);
if (isMapped)
init_minimize = false; // If it's already mapped, ignore hint
const NET::Properties properties =
NET::WMDesktop |
NET::WMState |
NET::WMWindowType |
NET::WMStrut |
NET::WMName |
NET::WMIconGeometry |
NET::WMIcon |
NET::WMPid |
NET::WMIconName;
const NET::Properties2 properties2 =
NET::WM2BlockCompositing |
NET::WM2WindowClass |
NET::WM2WindowRole |
NET::WM2UserTime |
NET::WM2StartupId |
NET::WM2ExtendedStrut |
NET::WM2Opacity |
NET::WM2FullscreenMonitors |
NET::WM2FrameOverlap;
info = new WinInfo(this, m_client, rootWindow(), properties, properties2);
m_colormap = attr->colormap;
getResourceClass();
getWmClientLeader();
getWmClientMachine();
getSyncCounter();
// First only read the caption text, so that setupWindowRules() can use it for matching,
// and only then really set the caption using setCaption(), which checks for duplicates etc.
// and also relies on rules already existing
cap_normal = readName();
2011-01-30 14:34:42 +00:00
setupWindowRules(false);
setCaption(cap_normal, true);
if (Xcb::Extensions::self()->isShapeAvailable())
xcb_shape_select_input(connection(), window(), true);
2011-01-30 14:34:42 +00:00
detectShape(window());
Add a script to enforce window decorations for GTK windows This is going to be a controversal change. It enforces KWin decorations on all client side decorated windows from GTK+. Unfortunately we are caught between a rock and a hard place. Keeping the status quo means having broken windows and a more or less broken window manager due to GTK+ including the shadow in the windows. This is no solution. Enforcing server side decorations visually breaks the windows. This is also no solution. So why do it? It's our task to provide the best possible user experience and KWin is a window manager which has always done great efforts to fix misbehaving windows. One can think of the focus stealing prevention, the window rules and lately the scripts. The best possible window management experience is our aim. This means we cannot leave the users with the broken windows from GTK. The issues we noticed were reported to GTK+ about 2 months ago and we are working on improving the situation. Unfortunately several issues are not yet addressed and others will only be addressed in the next GTK+ release. We are working on improving the NETWM spec (see [1]) to ensure that the client side decorated windows are not in a broken state. This means the enforcment is a temporary solution and will be re-evaluated with the next GTK release. I would prefer to not have to do such a change, if some of the bugs were fixed or GTK+ would not use client-side-decos on wms not yet supporting those all of this would be a no issue. For a complete list of the problems caused by GTK's decos see bug [2] and the linked bug reports from there. The change is done in a least inversive way in KWin. We just check for the property _GTK_FRAME_EXTENTS and create a Q_PROPERTY in Client for it. If we add support for the frame extents in future we would also need this. So it's not a change just for enforcing the decoration. The actual enforcing is done through a KWin script so users can still disable it. REVIEW: 119062 [1] https://mail.gnome.org/archives/wm-spec-list/2014-June/msg00002.html [2] https://bugzilla.gnome.org/show_bug.cgi?id=729721
2014-07-01 13:44:02 +00:00
detectGtkFrameExtents();
detectNoBorder();
fetchIconicName();
getWMHints(); // Needs to be done before readTransient() because of reading the group
2011-01-30 14:34:42 +00:00
modal = (info->state() & NET::Modal) != 0; // Needs to be valid before handling groups
readTransient();
getIcons();
getWindowProtocols();
getWmNormalHints(); // Get xSizeHint
getMotifHints();
getWmOpaqueRegion();
getSkipCloseAnimation();
// TODO: Try to obey all state information from info->state()
2011-01-30 14:34:42 +00:00
original_skip_taskbar = skip_taskbar = (info->state() & NET::SkipTaskbar) != 0;
skip_pager = (info->state() & NET::SkipPager) != 0;
updateFirstInTabBox();
setupCompositing();
KStartupInfoId asn_id;
KStartupInfoData asn_data;
2011-01-30 14:34:42 +00:00
bool asn_valid = workspace()->checkStartupNotification(window(), asn_id, asn_data);
// Make sure that the input window is created before we update the stacking order
updateInputWindow();
2011-01-30 14:34:42 +00:00
workspace()->updateClientLayer(this);
2011-01-30 14:34:42 +00:00
SessionInfo* session = workspace()->takeSessionInfo(this);
if (session) {
init_minimize = session->minimized;
noborder = session->noBorder;
2011-01-30 14:34:42 +00:00
}
2011-01-30 14:34:42 +00:00
setShortcut(rules()->checkShortcut(session ? session->shortcut : QString(), true));
2011-01-30 14:34:42 +00:00
init_minimize = rules()->checkMinimize(init_minimize, !isMapped);
noborder = rules()->checkNoBorder(noborder, !isMapped);
checkActivities();
// Initial desktop placement
2011-01-30 14:34:42 +00:00
if (session) {
desk = session->desktop;
2011-01-30 14:34:42 +00:00
if (session->onAllDesktops)
desk = NET::OnAllDesktops;
setOnActivities(session->activities);
2011-01-30 14:34:42 +00:00
} else {
// If this window is transient, ensure that it is opened on the
// same window as its parent. this is necessary when an application
// starts up on a different desktop than is currently displayed
2011-01-30 14:34:42 +00:00
if (isTransient()) {
ClientList mainclients = mainClients();
bool on_current = false;
bool on_all = false;
Client* maincl = NULL;
// This is slightly duplicated from Placement::placeOnMainWindow()
2011-01-30 14:34:42 +00:00
for (ClientList::ConstIterator it = mainclients.constBegin();
it != mainclients.constEnd();
++it) {
if (mainclients.count() > 1 && (*it)->isSpecialWindow())
continue; // Don't consider toolbars etc when placing
maincl = *it;
2011-01-30 14:34:42 +00:00
if ((*it)->isOnCurrentDesktop())
on_current = true;
2011-01-30 14:34:42 +00:00
if ((*it)->isOnAllDesktops())
on_all = true;
2011-01-30 14:34:42 +00:00
}
if (on_all)
desk = NET::OnAllDesktops;
2011-01-30 14:34:42 +00:00
else if (on_current)
desk = VirtualDesktopManager::self()->current();
2011-01-30 14:34:42 +00:00
else if (maincl != NULL)
desk = maincl->desktop();
2011-01-30 14:34:42 +00:00
if (maincl)
setOnActivities(maincl->activities());
2011-01-30 14:34:42 +00:00
}
if (info->desktop())
desk = info->desktop(); // Window had the initial desktop property, force it
2011-01-30 14:34:42 +00:00
if (desktop() == 0 && asn_valid && asn_data.desktop() != 0)
desk = asn_data.desktop();
#ifdef KWIN_BUILD_ACTIVITIES
if (!isMapped && !noborder && isNormalWindow() && !activitiesDefined) {
//a new, regular window, when we're not recovering from a crash,
//and it hasn't got an activity. let's try giving it the current one.
//TODO: decide whether to keep this before the 4.6 release
//TODO: if we are keeping it (at least as an option), replace noborder checking
//with a public API for setting windows to be on all activities.
//something like KWindowSystem::setOnAllActivities or
//KActivityConsumer::setOnAllActivities
setOnActivity(Activities::self()->current(), true);
}
#endif
2011-01-30 14:34:42 +00:00
}
2011-01-30 14:34:42 +00:00
if (desk == 0) // Assume window wants to be visible on the current desktop
2013-01-08 10:04:07 +00:00
desk = isDesktop() ? static_cast<int>(NET::OnAllDesktops) : VirtualDesktopManager::self()->current();
2011-01-30 14:34:42 +00:00
desk = rules()->checkDesktop(desk, !isMapped);
if (desk != NET::OnAllDesktops) // Do range check
desk = qBound(1, desk, static_cast<int>(VirtualDesktopManager::self()->count()));
2011-01-30 14:34:42 +00:00
info->setDesktop(desk);
workspace()->updateOnAllDesktopsOfTransients(this); // SELI TODO
//onAllDesktopsChange(); // Decoration doesn't exist here yet
QString activitiesList;
activitiesList = rules()->checkActivity(activitiesList, !isMapped);
if (!activitiesList.isEmpty())
setOnActivities(activitiesList.split(QStringLiteral(",")));
QRect geom(windowGeometry.rect());
bool placementDone = false;
2011-01-30 14:34:42 +00:00
if (session)
geom = session->geometry;
QRect area;
bool partial_keep_in_area = isMapped || session;
2011-01-30 14:34:42 +00:00
if (isMapped || session)
area = workspace()->clientArea(FullArea, geom.center(), desktop());
else {
int screen = asn_data.xinerama() == -1 ? screens()->current() : asn_data.xinerama();
2012-08-24 16:48:50 +00:00
screen = rules()->checkScreen(screen, !isMapped);
area = workspace()->clientArea(PlacementArea, screens()->geometry(screen).center(), desktop());
}
2011-01-30 14:34:42 +00:00
if (int type = checkFullScreenHack(geom)) {
fullscreen_mode = FullScreenHack;
2011-01-30 14:34:42 +00:00
if (rules()->checkStrictGeometry(false)) {
geom = type == 2 // 1 = It's xinerama-aware fullscreen hack, 2 = It's full area
2011-01-30 14:34:42 +00:00
? workspace()->clientArea(FullArea, geom.center(), desktop())
: workspace()->clientArea(ScreenArea, geom.center(), desktop());
} else
geom = workspace()->clientArea(FullScreenArea, geom.center(), desktop());
placementDone = true;
2011-01-30 14:34:42 +00:00
}
2011-01-30 14:34:42 +00:00
if (isDesktop())
// KWin doesn't manage desktop windows
placementDone = true;
bool usePosition = false;
2011-01-30 14:34:42 +00:00
if (isMapped || session || placementDone)
placementDone = true; // Use geometry
2011-01-30 14:34:42 +00:00
else if (isTransient() && !isUtility() && !isDialog() && !isSplash())
usePosition = true;
2011-01-30 14:34:42 +00:00
else if (isTransient() && !hasNETSupport())
usePosition = true;
2011-01-30 14:34:42 +00:00
else if (isDialog() && hasNETSupport()) {
// If the dialog is actually non-NETWM transient window, don't try to apply placement to it,
// it breaks with too many things (xmms, display)
if (mainClients().count() >= 1) {
#if 1
// #78082 - Ok, it seems there are after all some cases when an application has a good
// reason to specify a position for its dialog. Too bad other WMs have never bothered
// with placement for dialogs, so apps always specify positions for their dialogs,
// including such silly positions like always centered on the screen or under mouse.
// Using ignoring requested position in window-specific settings helps, and now
// there's also _NET_WM_FULL_PLACEMENT.
usePosition = true;
#else
; // Force using placement policy
#endif
2011-01-30 14:34:42 +00:00
} else
usePosition = true;
2011-01-30 14:34:42 +00:00
} else if (isSplash())
; // Force using placement policy
else
usePosition = true;
if (!rules()->checkIgnoreGeometry(!usePosition, true)) {
if (((xSizeHint.flags & PPosition)) ||
2011-01-30 14:34:42 +00:00
(xSizeHint.flags & USPosition)) {
placementDone = true;
// Disobey xinerama placement option for now (#70943)
2011-01-30 14:34:42 +00:00
area = workspace()->clientArea(PlacementArea, geom.center(), desktop());
}
2011-01-30 14:34:42 +00:00
}
//if ( true ) // Size is always obeyed for now, only with constraints applied
// if (( xSizeHint.flags & USSize ) || ( xSizeHint.flags & PSize ))
// {
// // Keep in mind that we now actually have a size :-)
// }
2011-01-30 14:34:42 +00:00
if (xSizeHint.flags & PMaxSize)
geom.setSize(geom.size().boundedTo(
rules()->checkMaxSize(QSize(xSizeHint.max_width, xSizeHint.max_height))));
if (xSizeHint.flags & PMinSize)
geom.setSize(geom.size().expandedTo(
rules()->checkMinSize(QSize(xSizeHint.min_width, xSizeHint.min_height))));
2011-01-30 14:34:42 +00:00
if (isMovable() && (geom.x() > area.right() || geom.y() > area.bottom()))
placementDone = false; // Weird, do not trust.
2011-01-30 14:34:42 +00:00
if (placementDone)
move(geom.x(), geom.y()); // Before gravitating
// Create client group if the window will have a decoration
bool dontKeepInArea = false;
setTabGroup(NULL);
if (!noBorder() && false) {
const bool autogrouping = rules()->checkAutogrouping(options->isAutogroupSimilarWindows());
const bool autogroupInFg = rules()->checkAutogroupInForeground(options->isAutogroupInForeground());
// Automatically add to previous groups on session restore
if (session && session->tabGroupClient && !workspace()->hasClient(session->tabGroupClient))
session->tabGroupClient = NULL;
if (session && session->tabGroupClient && session->tabGroupClient != this) {
tabBehind(session->tabGroupClient, autogroupInFg);
} else if (isMapped && autogrouping) {
// If the window is already mapped (Restarted KWin) add any windows that already have the
// same geometry to the same client group. (May incorrectly handle maximized windows)
foreach (Client *other, workspace()->clientList()) {
if (other->maximizeMode() != MaximizeFull &&
geom == QRect(other->pos(), other->clientSize()) &&
desk == other->desktop() && activities() == other->activities()) {
tabBehind(other, autogroupInFg);
break;
}
2011-01-30 14:34:42 +00:00
}
}
if (!(tab_group || isMapped || session)) {
2011-01-30 14:34:42 +00:00
// Attempt to automatically group similar windows
Client* similar = findAutogroupCandidate();
if (similar && !similar->noBorder()) {
if (autogroupInFg) {
similar->setDesktop(desk); // can happen when grouping by id. ...
similar->setMinimized(false); // ... or anyway - still group, but "here" and visible
}
if (!similar->isMinimized()) { // do not attempt to tab in background of a hidden group
geom = QRect(similar->pos() + similar->clientPos(), similar->clientSize());
updateDecoration(false);
if (tabBehind(similar, autogroupInFg)) {
// Don't move entire group
geom = QRect(similar->pos() + similar->clientPos(), similar->clientSize());
placementDone = true;
dontKeepInArea = true;
}
}
}
}
2011-01-30 14:34:42 +00:00
}
2011-01-30 14:34:42 +00:00
updateDecoration(false); // Also gravitates
// TODO: Is CentralGravity right here, when resizing is done after gravitating?
2011-01-30 14:34:42 +00:00
plainResize(rules()->checkSize(sizeForClientSize(geom.size()), !isMapped));
2011-01-30 14:34:42 +00:00
QPoint forced_pos = rules()->checkPosition(invalidPoint, !isMapped);
if (forced_pos != invalidPoint) {
move(forced_pos);
placementDone = true;
// Don't keep inside workarea if the window has specially configured position
partial_keep_in_area = true;
2011-01-30 14:34:42 +00:00
area = workspace()->clientArea(FullArea, geom.center(), desktop());
}
if (!placementDone) {
// Placement needs to be after setting size
Placement::self()->place(this, area);
dontKeepInArea = true;
placementDone = true;
2011-01-30 14:34:42 +00:00
}
// bugs #285967, #286146, #183694
// geometry() now includes the requested size and the decoration and is at the correct screen/position (hopefully)
// Maximization for oversized windows must happen NOW.
// If we effectively pass keepInArea(), the window will resizeWithChecks() - i.e. constrained
// to the combo of all screen MINUS all struts on the edges
// If only one screen struts, this will affect screens as a side-effect, the window is artificailly shrinked
// below the screen size and as result no more maximized what breaks KMainWindow's stupid width+1, height+1 hack
// TODO: get KMainWindow a correct state storage what will allow to store the restore size as well.
if (!session) { // has a better handling of this
geom_restore = geometry(); // Remember restore geometry
if (isMaximizable() && (width() >= area.width() || height() >= area.height())) {
// Window is too large for the screen, maximize in the
// directions necessary
const QSize ss = workspace()->clientArea(ScreenArea, area.center(), desktop()).size();
const QRect fsa = workspace()->clientArea(FullArea, geom.center(), desktop());
const QSize cs = clientSize();
int pseudo_max = Client::MaximizeRestore;
if (width() >= area.width())
pseudo_max |= Client::MaximizeHorizontal;
if (height() >= area.height())
pseudo_max |= Client::MaximizeVertical;
// heuristics:
// if decorated client is smaller than the entire screen, the user might want to move it around (multiscreen)
// in this case, if the decorated client is bigger than the screen (+1), we don't take this as an
// attempt for maximization, but just constrain the size (the window simply wants to be bigger)
// NOTICE
// i intended a second check on cs < area.size() ("the managed client ("minus border") is smaller
// than the workspace") but gtk / gimp seems to store it's size including the decoration,
// thus a former maximized window wil become non-maximized
bool keepInFsArea = false;
if (width() < fsa.width() && (cs.width() > ss.width()+1)) {
pseudo_max &= ~Client::MaximizeHorizontal;
keepInFsArea = true;
}
if (height() < fsa.height() && (cs.height() > ss.height()+1)) {
pseudo_max &= ~Client::MaximizeVertical;
keepInFsArea = true;
}
if (pseudo_max != Client::MaximizeRestore) {
maximize((MaximizeMode)pseudo_max);
// from now on, care about maxmode, since the maximization call will override mode for fix aspects
dontKeepInArea |= (max_mode == Client::MaximizeFull);
geom_restore = QRect(); // Use placement when unmaximizing ...
if (!(max_mode & Client::MaximizeVertical)) {
geom_restore.setY(y()); // ...but only for horizontal direction
geom_restore.setHeight(height());
}
if (!(max_mode & Client::MaximizeHorizontal)) {
geom_restore.setX(x()); // ...but only for vertical direction
geom_restore.setWidth(width());
}
}
if (keepInFsArea)
keepInArea(fsa, partial_keep_in_area);
}
}
2011-01-30 14:34:42 +00:00
if ((!isSpecialWindow() || isToolbar()) && isMovable() && !dontKeepInArea)
keepInArea(area, partial_keep_in_area);
updateShape();
// CT: Extra check for stupid jdk 1.3.1. But should make sense in general
// if client has initial state set to Iconic and is transient with a parent
// window that is not Iconic, set init_state to Normal
2011-01-30 14:34:42 +00:00
if (init_minimize && isTransient()) {
ClientList mainclients = mainClients();
2011-01-30 14:34:42 +00:00
for (ClientList::ConstIterator it = mainclients.constBegin();
it != mainclients.constEnd();
++it)
if ((*it)->isShown(true))
init_minimize = false; // SELI TODO: Even e.g. for NET::Utility?
2011-01-30 14:34:42 +00:00
}
// If a dialog is shown for minimized window, minimize it too
if (!init_minimize && isTransient() && mainClients().count() > 0 && !workspace()->sessionSaving()) {
bool visible_parent = false;
// Use allMainClients(), to include also main clients of group transients
// that have been optimized out in Client::checkGroupTransients()
ClientList mainclients = allMainClients();
2011-01-30 14:34:42 +00:00
for (ClientList::ConstIterator it = mainclients.constBegin();
it != mainclients.constEnd();
++it)
if ((*it)->isShown(true))
visible_parent = true;
2011-01-30 14:34:42 +00:00
if (!visible_parent) {
init_minimize = true;
demandAttention();
}
2011-01-30 14:34:42 +00:00
}
2011-01-30 14:34:42 +00:00
if (init_minimize)
minimize(true); // No animation
// Other settings from the previous session
2011-01-30 14:34:42 +00:00
if (session) {
// Session restored windows are not considered to be new windows WRT rules,
// I.e. obey only forcing rules
2011-01-30 14:34:42 +00:00
setKeepAbove(session->keepAbove);
setKeepBelow(session->keepBelow);
setSkipTaskbar(session->skipTaskbar, true);
setSkipPager(session->skipPager);
setSkipSwitcher(session->skipSwitcher);
setShade(session->shaded ? ShadeNormal : ShadeNone);
setOpacity(session->opacity);
geom_restore = session->restore;
2011-01-30 14:34:42 +00:00
if (session->maximized != MaximizeRestore) {
maximize(MaximizeMode(session->maximized));
}
if (session->fullscreen == FullScreenHack)
; // Nothing, this should be already set again above
2011-01-30 14:34:42 +00:00
else if (session->fullscreen != FullScreenNone) {
setFullScreen(true, false);
geom_fs_restore = session->fsrestore;
}
2011-01-30 14:34:42 +00:00
} else {
// Window may want to be maximized
// done after checking that the window isn't larger than the workarea, so that
// the restore geometry from the checks above takes precedence, and window
// isn't restored larger than the workarea
MaximizeMode maxmode = static_cast<MaximizeMode>(
2011-01-30 14:34:42 +00:00
((info->state() & NET::MaxVert) ? MaximizeVertical : 0) |
((info->state() & NET::MaxHoriz) ? MaximizeHorizontal : 0));
MaximizeMode forced_maxmode = rules()->checkMaximize(maxmode, !isMapped);
// Either hints were set to maximize, or is forced to maximize,
// or is forced to non-maximize and hints were set to maximize
2011-01-30 14:34:42 +00:00
if (forced_maxmode != MaximizeRestore || maxmode != MaximizeRestore)
maximize(forced_maxmode);
// Read other initial states
2011-01-30 14:34:42 +00:00
setShade(rules()->checkShade(info->state() & NET::Shaded ? ShadeNormal : ShadeNone, !isMapped));
setKeepAbove(rules()->checkKeepAbove(info->state() & NET::KeepAbove, !isMapped));
setKeepBelow(rules()->checkKeepBelow(info->state() & NET::KeepBelow, !isMapped));
setSkipTaskbar(rules()->checkSkipTaskbar(info->state() & NET::SkipTaskbar, !isMapped), true);
setSkipPager(rules()->checkSkipPager(info->state() & NET::SkipPager, !isMapped));
setSkipSwitcher(rules()->checkSkipSwitcher(false, !isMapped));
if (info->state() & NET::DemandsAttention)
demandAttention();
2011-01-30 14:34:42 +00:00
if (info->state() & NET::Modal)
setModal(true);
if (fullscreen_mode != FullScreenHack)
2011-01-30 14:34:42 +00:00
setFullScreen(rules()->checkFullScreen(info->state() & NET::FullScreen, !isMapped), false);
}
2011-01-30 14:34:42 +00:00
updateAllowedActions(true);
// Set initial user time directly
m_userTime = readUserTimeMapTimestamp(asn_valid ? &asn_id : NULL, asn_valid ? &asn_data : NULL, session);
group()->updateUserTime(m_userTime); // And do what Client::updateUserTime() does
// This should avoid flicker, because real restacking is done
// only after manage() finishes because of blocking, but the window is shown sooner
m_frame.lower();
2011-01-30 14:34:42 +00:00
if (session && session->stackingOrder != -1) {
sm_stacking_order = session->stackingOrder;
2011-01-30 14:34:42 +00:00
workspace()->restoreSessionStackingOrder(this);
}
2011-01-30 14:34:42 +00:00
if (compositing())
// Sending ConfigureNotify is done when setting mapping state below,
// Getting the first sync response means window is ready for compositing
sendSyncRequest();
else
ready_for_painting = true; // set to true in case compositing is turned on later. bug #160393
if (isShown(true)) {
bool allow;
2011-01-30 14:34:42 +00:00
if (session)
allow = session->active &&
2011-01-30 14:34:42 +00:00
(!workspace()->wasUserInteraction() || workspace()->activeClient() == NULL ||
workspace()->activeClient()->isDesktop());
else
2011-01-30 14:34:42 +00:00
allow = workspace()->allowClientActivation(this, userTime(), false);
if (!(isMapped || session)) {
if (workspace()->sessionSaving()) {
/*
* If we get a new window during session saving, we assume it's some 'save file?' dialog
* which the user really needs to see (to know why logout's stalled).
*
* Given the current session management protocol, I can't see a nicer way of doing this.
* Someday I'd like to see a protocol that tells the windowmanager who's doing SessionInteract.
*/
needsSessionInteract = true;
//show the parent too
ClientList mainclients = mainClients();
2011-01-30 14:34:42 +00:00
for (ClientList::ConstIterator it = mainclients.constBegin();
it != mainclients.constEnd(); ++it) {
(*it)->setSessionInteract(true);
(*it)->unminimize();
}
} else if (allow) {
// also force if activation is allowed
if (!isOnCurrentDesktop() && options->focusPolicyIsReasonable()) {
VirtualDesktopManager::self()->setCurrent(desktop());
}
/*if (!isOnCurrentActivity()) {
workspace()->setCurrentActivity( activities().first() );
} FIXME no such method*/
}
}
resetShowingDesktop(options->isShowDesktopIsMinimizeAll());
2011-01-30 14:34:42 +00:00
if (isOnCurrentDesktop() && !isMapped && !allow && (!session || session->stackingOrder < 0))
workspace()->restackClientUnderActive(this);
updateVisibility();
2011-01-30 14:34:42 +00:00
if (!isMapped) {
if (allow && isOnCurrentDesktop()) {
if (!isSpecialWindow())
if (options->focusPolicyIsReasonable() && wantsTabFocus())
workspace()->requestFocus(this);
} else if (!session && !isSpecialWindow())
demandAttention();
}
} else
updateVisibility();
2011-01-30 14:34:42 +00:00
assert(mapping_state != Withdrawn);
m_managed = true;
2011-01-30 14:34:42 +00:00
blockGeometryUpdates(false);
if (m_userTime == XCB_TIME_CURRENT_TIME || m_userTime == -1U) {
2011-01-30 14:34:42 +00:00
// No known user time, set something old
m_userTime = xTime() - 1000000;
if (m_userTime == XCB_TIME_CURRENT_TIME || m_userTime == -1U) // Let's be paranoid
m_userTime = xTime() - 1000000 + 10;
2011-01-30 14:34:42 +00:00
}
//sendSyntheticConfigureNotify(); // Done when setting mapping state
delete session;
ungrabXServer();
client_rules.discardTemporary();
applyWindowRules(); // Just in case
RuleBook::self()->discardUsed(this, false); // Remove ApplyNow rules
updateWindowRules(Rules::All); // Was blocked while !isManaged()
setBlockingCompositing(info->isBlockingCompositing());
updateColorScheme();
Screenedge show support for Clients This provides a new protocol intended to be used by auto-hiding panels to make use of the centralized screen edges. To use it a Client can set an X11 property of type _KDE_NET_WM_SCREEN_EDGE_SHOW to KWin. As value it takes: * 0: top edge * 1: right edge * 2: bottom edge * 3: left edge KWin will hide the Client (hide because unmap or minimize would break it) and create an Edge. If that Edge gets triggered the Client is shown again and the property gets deleted. If the Client doesn't border the specified screen edge the Client gets shown immediately so that we never end in a situation that we cannot unhide the auto-hidden panel again. The exact process is described in the documentation of ScreenEdges. The Client can request to be shown again by deleting the property. If KWin gets restarted the state is read from the property and it is tried to create the edge as described. As this is a KWin specific extension we need to discuss what it means for Clients using this feature with other WMs: it does nothing. As the Client gets hidden by KWin and not by the Client, it just doesn't get hidden if the WM doesn't provide the feature. In case of an auto-hiding panel this seems like a good solution given that we don't want to hide it if we cannot unhide it. Of course there's the option for the Client to provide that feature itself and if that's wanted we would need to announce the feature in the _NET_SUPPORTED atom. At the moment that doesn't sound like being needed as Plasma doesn't want to provide an own implementation. The implementation comes with a small test application showing how the feature is intended to be used. REVIEW: 115910
2014-02-20 11:39:23 +00:00
updateShowOnScreenEdge();
// TODO: there's a small problem here - isManaged() depends on the mapping state,
// but this client is not yet in Workspace's client list at this point, will
// be only done in addClient()
emit clientManaging(this);
return true;
2011-01-30 14:34:42 +00:00
}
// Called only from manage()
void Client::embedClient(xcb_window_t w, xcb_visualid_t visualid, xcb_colormap_t colormap, uint8_t depth)
2011-01-30 14:34:42 +00:00
{
assert(m_client == XCB_WINDOW_NONE);
assert(frameId() == XCB_WINDOW_NONE);
assert(m_wrapper == XCB_WINDOW_NONE);
m_client.reset(w, false);
const uint32_t zero_value = 0;
xcb_connection_t *conn = connection();
// We don't want the window to be destroyed when we quit
xcb_change_save_set(conn, XCB_SET_MODE_INSERT, m_client);
m_client.selectInput(zero_value);
m_client.unmap();
m_client.setBorderWidth(zero_value);
// Note: These values must match the order in the xcb_cw_t enum
const uint32_t cw_values[] = {
0, // back_pixmap
0, // border_pixel
colormap, // colormap
Cursor::x11Cursor(Qt::ArrowCursor)
};
const uint32_t cw_mask = XCB_CW_BACK_PIXMAP | XCB_CW_BORDER_PIXEL |
XCB_CW_COLORMAP | XCB_CW_CURSOR;
const uint32_t common_event_mask = XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_KEY_RELEASE |
XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW |
XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE |
XCB_EVENT_MASK_BUTTON_MOTION | XCB_EVENT_MASK_POINTER_MOTION |
XCB_EVENT_MASK_KEYMAP_STATE |
XCB_EVENT_MASK_FOCUS_CHANGE |
XCB_EVENT_MASK_EXPOSURE |
XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT;
const uint32_t frame_event_mask = common_event_mask | XCB_EVENT_MASK_PROPERTY_CHANGE | XCB_EVENT_MASK_VISIBILITY_CHANGE;
const uint32_t wrapper_event_mask = common_event_mask | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY;
const uint32_t client_event_mask = XCB_EVENT_MASK_FOCUS_CHANGE | XCB_EVENT_MASK_PROPERTY_CHANGE |
XCB_EVENT_MASK_COLOR_MAP_CHANGE |
XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW |
XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_KEY_RELEASE;
// Create the frame window
xcb_window_t frame = xcb_generate_id(conn);
xcb_create_window(conn, depth, frame, rootWindow(), 0, 0, 1, 1, 0,
XCB_WINDOW_CLASS_INPUT_OUTPUT, visualid, cw_mask, cw_values);
m_frame.reset(frame);
m_frameWrapper.reset(QWindow::fromWinId(m_frame));
setWindowHandles(m_client);
// Create the wrapper window
xcb_window_t wrapperId = xcb_generate_id(conn);
xcb_create_window(conn, depth, wrapperId, frame, 0, 0, 1, 1, 0,
XCB_WINDOW_CLASS_INPUT_OUTPUT, visualid, cw_mask, cw_values);
m_wrapper.reset(wrapperId);
m_client.reparent(m_wrapper);
// We could specify the event masks when we create the windows, but the original
// Xlib code didn't. Let's preserve that behavior here for now so we don't end up
// receiving any unexpected events from the wrapper creation or the reparenting.
m_frame.selectInput(frame_event_mask);
m_wrapper.selectInput(wrapper_event_mask);
m_client.selectInput(client_event_mask);
updateMouseGrab();
2011-01-30 14:34:42 +00:00
}
// To accept "mainwindow#1" to "mainwindow#2"
static QByteArray truncatedWindowRole(QByteArray a)
{
int i = a.indexOf('#');
if (i == -1)
return a;
QByteArray b(a);
b.truncate(i);
return b;
}
Client* Client::findAutogroupCandidate() const
{
// Attempt to find a similar window to the input. If we find multiple possibilities that are in
// different groups then ignore all of them. This function is for automatic window grouping.
Client *found = NULL;
// See if the window has a group ID to match with
QString wGId = rules()->checkAutogroupById(QString());
if (!wGId.isEmpty()) {
foreach (Client *c, workspace()->clientList()) {
if (activities() != c->activities())
continue; // don't cross activities
if (wGId == c->rules()->checkAutogroupById(QString())) {
if (found && found->tabGroup() != c->tabGroup()) { // We've found two, ignore both
found = NULL;
break; // Continue to the next test
}
found = c;
}
}
if (found)
return found;
}
// If this is a transient window don't take a guess
if (isTransient())
return NULL;
// If we don't have an ID take a guess
if (rules()->checkAutogrouping(options->isAutogroupSimilarWindows())) {
QByteArray wRole = truncatedWindowRole(windowRole());
foreach (Client *c, workspace()->clientList()) {
if (desktop() != c->desktop() || activities() != c->activities())
continue;
QByteArray wRoleB = truncatedWindowRole(c->windowRole());
if (resourceClass() == c->resourceClass() && // Same resource class
wRole == wRoleB && // Same window role
c->isNormalWindow()) { // Normal window TODO: Can modal windows be "normal"?
if (found && found->tabGroup() != c->tabGroup()) // We've found two, ignore both
return NULL;
found = c;
}
}
}
return found;
}
} // namespace