Changeset View
Changeset View
Standalone View
Standalone View
xdgshellclient.cpp
Show All 14 Lines | |||||
15 | but WITHOUT ANY WARRANTY; without even the implied warranty of | 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
17 | GNU General Public License for more details. | 17 | GNU General Public License for more details. | ||
18 | 18 | | |||
19 | You should have received a copy of the GNU General Public License | 19 | You should have received a copy of the GNU General Public License | ||
20 | along with this program. If not, see <http://www.gnu.org/licenses/>. | 20 | along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
21 | *********************************************************************/ | 21 | *********************************************************************/ | ||
22 | #include "xdgshellclient.h" | 22 | #include "xdgshellclient.h" | ||
23 | #include "cursor.h" | | |||
24 | #include "decorations/decoratedclient.h" | | |||
25 | #include "decorations/decorationbridge.h" | | |||
26 | #include "deleted.h" | 23 | #include "deleted.h" | ||
27 | #include "placement.h" | | |||
28 | #include "screenedge.h" | 24 | #include "screenedge.h" | ||
29 | #include "screens.h" | 25 | #include "screens.h" | ||
26 | #include "subsurfacemonitor.h" | ||||
27 | #include "wayland_server.h" | ||||
28 | #include "workspace.h" | ||||
29 | | ||||
30 | #ifdef KWIN_BUILD_TABBOX | 30 | #ifdef KWIN_BUILD_TABBOX | ||
31 | #include "tabbox.h" | 31 | #include "tabbox.h" | ||
32 | #endif | 32 | #endif | ||
33 | #include "virtualdesktops.h" | | |||
34 | #include "wayland_server.h" | | |||
35 | #include "workspace.h" | | |||
36 | 33 | | |||
37 | #include <KDecoration2/DecoratedClient> | 34 | #include <KDecoration2/DecoratedClient> | ||
38 | #include <KDecoration2/Decoration> | 35 | #include <KDecoration2/Decoration> | ||
39 | #include <KWaylandServer/appmenu_interface.h> | 36 | #include <KWaylandServer/appmenu_interface.h> | ||
40 | #include <KWaylandServer/buffer_interface.h> | 37 | #include <KWaylandServer/buffer_interface.h> | ||
41 | #include <KWaylandServer/clientconnection.h> | 38 | #include <KWaylandServer/output_interface.h> | ||
42 | #include <KWaylandServer/display.h> | | |||
43 | #include <KWaylandServer/plasmashell_interface.h> | 39 | #include <KWaylandServer/plasmashell_interface.h> | ||
44 | #include <KWaylandServer/plasmawindowmanagement_interface.h> | | |||
45 | #include <KWaylandServer/seat_interface.h> | 40 | #include <KWaylandServer/seat_interface.h> | ||
46 | #include <KWaylandServer/server_decoration_interface.h> | 41 | #include <KWaylandServer/server_decoration_interface.h> | ||
47 | #include <KWaylandServer/server_decoration_palette_interface.h> | 42 | #include <KWaylandServer/server_decoration_palette_interface.h> | ||
48 | #include <KWaylandServer/shadow_interface.h> | | |||
49 | #include <KWaylandServer/surface_interface.h> | 43 | #include <KWaylandServer/surface_interface.h> | ||
50 | #include <KWaylandServer/xdgdecoration_interface.h> | 44 | #include <KWaylandServer/xdgdecoration_v1_interface.h> | ||
51 | 45 | #include <KWaylandServer/xdgshell_interface.h> | |||
52 | #include <QFileInfo> | | |||
53 | | ||||
54 | #include <sys/types.h> | | |||
55 | #include <unistd.h> | | |||
56 | | ||||
57 | #include <csignal> | | |||
58 | | ||||
59 | Q_DECLARE_METATYPE(NET::WindowType) | | |||
60 | 46 | | |||
61 | using namespace KWaylandServer; | 47 | using namespace KWaylandServer; | ||
62 | 48 | | |||
63 | namespace KWin | 49 | namespace KWin | ||
64 | { | 50 | { | ||
65 | 51 | | |||
66 | XdgShellClient::XdgShellClient(XdgShellSurfaceInterface *surface) | 52 | XdgSurfaceClient::XdgSurfaceClient(XdgSurfaceInterface *shellSurface) | ||
67 | : AbstractClient() | 53 | : WaylandClient(shellSurface->surface()) | ||
68 | , m_xdgShellToplevel(surface) | 54 | , m_shellSurface(shellSurface) | ||
69 | , m_xdgShellPopup(nullptr) | 55 | , m_configureTimer(new QTimer(this)) | ||
70 | { | | |||
71 | setSurface(surface->surface()); | | |||
72 | init(); | | |||
73 | } | | |||
74 | | ||||
75 | XdgShellClient::XdgShellClient(XdgShellPopupInterface *surface) | | |||
76 | : AbstractClient() | | |||
77 | , m_xdgShellToplevel(nullptr) | | |||
78 | , m_xdgShellPopup(surface) | | |||
79 | { | | |||
80 | setSurface(surface->surface()); | | |||
81 | init(); | | |||
82 | } | | |||
83 | | ||||
84 | XdgShellClient::~XdgShellClient() = default; | | |||
85 | | ||||
86 | void XdgShellClient::init() | | |||
87 | { | 56 | { | ||
88 | m_requestGeometryBlockCounter++; | | |||
89 | | ||||
90 | connect(this, &XdgShellClient::desktopFileNameChanged, this, &XdgShellClient::updateIcon); | | |||
91 | createWindowId(); | | |||
92 | setupCompositing(); | 57 | setupCompositing(); | ||
93 | updateIcon(); | | |||
94 | | ||||
95 | // TODO: Initialize with null rect. | | |||
96 | m_frameGeometry = QRect(0, 0, -1, -1); | | |||
97 | m_windowGeometry = QRect(0, 0, -1, -1); | | |||
98 | | ||||
99 | if (waylandServer()->inputMethodConnection() == surface()->client()) { | | |||
100 | m_windowType = NET::OnScreenDisplay; | | |||
101 | } | | |||
102 | 58 | | |||
103 | connect(surface(), &SurfaceInterface::unmapped, this, &XdgShellClient::unmap); | 59 | connect(shellSurface, &XdgSurfaceInterface::configureAcknowledged, | ||
104 | connect(surface(), &SurfaceInterface::unbound, this, &XdgShellClient::destroyClient); | 60 | this, &XdgSurfaceClient::handleConfigureAcknowledged); | ||
105 | connect(surface(), &SurfaceInterface::destroyed, this, &XdgShellClient::destroyClient); | 61 | connect(shellSurface->surface(), &SurfaceInterface::committed, | ||
62 | this, &XdgSurfaceClient::handleCommit); | ||||
63 | connect(shellSurface->surface(), &SurfaceInterface::shadowChanged, | ||||
64 | this, &XdgSurfaceClient::updateShadow); | ||||
65 | connect(shellSurface->surface(), &SurfaceInterface::unmapped, | ||||
66 | this, &XdgSurfaceClient::internalUnmap); | ||||
67 | connect(shellSurface->surface(), &SurfaceInterface::unbound, | ||||
68 | this, &XdgSurfaceClient::destroyClient); | ||||
69 | connect(shellSurface->surface(), &SurfaceInterface::destroyed, | ||||
70 | this, &XdgSurfaceClient::destroyClient); | ||||
106 | 71 | | |||
107 | if (m_xdgShellToplevel) { | 72 | // The effective window geometry is determined by two things: (a) the rectangle that bounds | ||
108 | connect(m_xdgShellToplevel, &XdgShellSurfaceInterface::destroyed, this, &XdgShellClient::destroyClient); | 73 | // the main surface and all of its sub-surfaces, (b) the client-specified window geometry, if | ||
109 | connect(m_xdgShellToplevel, &XdgShellSurfaceInterface::configureAcknowledged, this, &XdgShellClient::handleConfigureAcknowledged); | 74 | // any. If the client hasn't provided the window geometry, we fallback to the bounding sub- | ||
75 | // surface rectangle. If the client has provided the window geometry, we intersect it with | ||||
76 | // the bounding rectangle and that will be the effective window geometry. It's worth to point | ||||
77 | // out that geometry updates do not occur that frequently, so we don't need to recompute the | ||||
78 | // bounding geometry every time the client commits the surface. | ||||
110 | 79 | | |||
111 | m_caption = m_xdgShellToplevel->title().simplified(); | 80 | SubSurfaceMonitor *treeMonitor = new SubSurfaceMonitor(surface(), this); | ||
112 | connect(m_xdgShellToplevel, &XdgShellSurfaceInterface::titleChanged, this, &XdgShellClient::handleWindowTitleChanged); | | |||
113 | QTimer::singleShot(0, this, &XdgShellClient::updateCaption); | | |||
114 | 81 | | |||
115 | connect(m_xdgShellToplevel, &XdgShellSurfaceInterface::moveRequested, this, &XdgShellClient::handleMoveRequested); | 82 | connect(treeMonitor, &SubSurfaceMonitor::subSurfaceAdded, | ||
116 | connect(m_xdgShellToplevel, &XdgShellSurfaceInterface::resizeRequested, this, &XdgShellClient::handleResizeRequested); | 83 | this, &XdgSurfaceClient::setHaveNextWindowGeometry); | ||
84 | connect(treeMonitor, &SubSurfaceMonitor::subSurfaceRemoved, | ||||
85 | this, &XdgSurfaceClient::setHaveNextWindowGeometry); | ||||
86 | connect(treeMonitor, &SubSurfaceMonitor::subSurfaceMoved, | ||||
87 | this, &XdgSurfaceClient::setHaveNextWindowGeometry); | ||||
88 | connect(treeMonitor, &SubSurfaceMonitor::subSurfaceResized, | ||||
89 | this, &XdgSurfaceClient::setHaveNextWindowGeometry); | ||||
90 | connect(shellSurface, &XdgSurfaceInterface::windowGeometryChanged, | ||||
91 | this, &XdgSurfaceClient::setHaveNextWindowGeometry); | ||||
92 | connect(surface(), &SurfaceInterface::sizeChanged, | ||||
93 | this, &XdgSurfaceClient::setHaveNextWindowGeometry); | ||||
117 | 94 | | |||
118 | // Determine the resource name, this is inspired from ICCCM 4.1.2.5 | 95 | // Configure events are not sent immediately, but rather scheduled to be sent when the event | ||
119 | // the binary name of the invoked client. | 96 | // loop is about to be idle. By doing this, we can avoid sending configure events that do | ||
120 | QFileInfo info{m_xdgShellToplevel->client()->executablePath()}; | 97 | // nothing, and implementation-wise, it's simpler. | ||
121 | QByteArray resourceName; | | |||
122 | if (info.exists()) { | | |||
123 | resourceName = info.fileName().toUtf8(); | | |||
124 | } | | |||
125 | setResourceClass(resourceName, m_xdgShellToplevel->windowClass()); | | |||
126 | setDesktopFileName(m_xdgShellToplevel->windowClass()); | | |||
127 | connect(m_xdgShellToplevel, &XdgShellSurfaceInterface::windowClassChanged, this, &XdgShellClient::handleWindowClassChanged); | | |||
128 | 98 | | |||
129 | connect(m_xdgShellToplevel, &XdgShellSurfaceInterface::minimizeRequested, this, &XdgShellClient::handleMinimizeRequested); | 99 | m_configureTimer->setSingleShot(true); | ||
130 | connect(m_xdgShellToplevel, &XdgShellSurfaceInterface::maximizedChanged, this, &XdgShellClient::handleMaximizeRequested); | 100 | connect(m_configureTimer, &QTimer::timeout, this, &XdgSurfaceClient::sendConfigure); | ||
131 | connect(m_xdgShellToplevel, &XdgShellSurfaceInterface::fullscreenChanged, this, &XdgShellClient::handleFullScreenRequested); | | |||
132 | connect(m_xdgShellToplevel, &XdgShellSurfaceInterface::windowMenuRequested, this, &XdgShellClient::handleWindowMenuRequested); | | |||
133 | connect(m_xdgShellToplevel, &XdgShellSurfaceInterface::transientForChanged, this, &XdgShellClient::handleTransientForChanged); | | |||
134 | connect(m_xdgShellToplevel, &XdgShellSurfaceInterface::windowGeometryChanged, this, &XdgShellClient::handleWindowGeometryChanged); | | |||
135 | 101 | | |||
136 | auto global = static_cast<XdgShellInterface *>(m_xdgShellToplevel->global()); | 102 | // Unfortunately, AbstractClient::checkWorkspacePosition() operates on the geometry restore | ||
137 | connect(global, &XdgShellInterface::pingDelayed, this, &XdgShellClient::handlePingDelayed); | 103 | // so we need to initialize it with some reasonable value; otherwise bad things will happen | ||
138 | connect(global, &XdgShellInterface::pingTimeout, this, &XdgShellClient::handlePingTimeout); | 104 | // when we want to decorate the client or move the client to another screen. This is a hack. | ||
139 | connect(global, &XdgShellInterface::pongReceived, this, &XdgShellClient::handlePongReceived); | | |||
140 | 105 | | |||
141 | auto configure = [this] { | 106 | connect(this, &XdgSurfaceClient::frameGeometryChanged, | ||
142 | if (m_closing) { | 107 | this, &XdgSurfaceClient::updateGeometryRestoreHack); | ||
143 | return; | | |||
144 | } | | |||
145 | if (m_requestGeometryBlockCounter != 0 || areGeometryUpdatesBlocked()) { | | |||
146 | return; | | |||
147 | } | | |||
148 | m_xdgShellToplevel->configure(xdgSurfaceStates(), m_requestedClientSize); | | |||
149 | }; | | |||
150 | connect(this, &AbstractClient::activeChanged, this, configure); | | |||
151 | connect(this, &AbstractClient::clientStartUserMovedResized, this, configure); | | |||
152 | connect(this, &AbstractClient::clientFinishUserMovedResized, this, configure); | | |||
153 | | ||||
154 | connect(this, &XdgShellClient::frameGeometryChanged, this, &XdgShellClient::updateClientOutputs); | | |||
155 | connect(screens(), &Screens::changed, this, &XdgShellClient::updateClientOutputs); | | |||
156 | } else if (m_xdgShellPopup) { | | |||
157 | connect(m_xdgShellPopup, &XdgShellPopupInterface::configureAcknowledged, this, &XdgShellClient::handleConfigureAcknowledged); | | |||
158 | connect(m_xdgShellPopup, &XdgShellPopupInterface::grabRequested, this, &XdgShellClient::handleGrabRequested); | | |||
159 | connect(m_xdgShellPopup, &XdgShellPopupInterface::destroyed, this, &XdgShellClient::destroyClient); | | |||
160 | connect(m_xdgShellPopup, &XdgShellPopupInterface::windowGeometryChanged, this, &XdgShellClient::handleWindowGeometryChanged); | | |||
161 | } | 108 | } | ||
162 | 109 | | |||
163 | // set initial desktop | 110 | XdgSurfaceClient::~XdgSurfaceClient() | ||
164 | setDesktop(VirtualDesktopManager::self()->current()); | 111 | { | ||
165 | 112 | qDeleteAll(m_configureEvents); | |||
davidedmundson: We leak the m_lastAckedConfigure if someone calls ackConfigure and gets deleted before commit. | |||||
Yes, I didn't want to use QScopedPointer because it's not a movable type. On the other hand, I considered QSharedPointer to be an overkill so I decided to do memory management the hard way. Anyway, I think we could use QScopedPointer for storing the last acknowledged configure event. zzag: Yes, I didn't want to use QScopedPointer because it's not a movable type. On the other hand, I… | |||||
166 | // setup shadow integration | | |||
167 | updateShadow(); | | |||
168 | connect(surface(), &SurfaceInterface::shadowChanged, this, &Toplevel::updateShadow); | | |||
169 | | ||||
170 | connect(waylandServer(), &WaylandServer::foreignTransientChanged, this, [this](KWaylandServer::SurfaceInterface *child) { | | |||
171 | if (child == surface()) { | | |||
172 | handleTransientForChanged(); | | |||
173 | } | 113 | } | ||
174 | }); | | |||
175 | handleTransientForChanged(); | | |||
176 | | ||||
177 | AbstractClient::updateColorScheme(QString()); | | |||
178 | 114 | | |||
179 | connect(surface(), &SurfaceInterface::committed, this, &XdgShellClient::finishInit); | 115 | QRect XdgSurfaceClient::requestedFrameGeometry() const | ||
116 | { | ||||
117 | return m_requestedFrameGeometry; | ||||
180 | } | 118 | } | ||
181 | 119 | | |||
182 | void XdgShellClient::finishInit() | 120 | QPoint XdgSurfaceClient::requestedPos() const | ||
183 | { | 121 | { | ||
184 | disconnect(surface(), &SurfaceInterface::committed, this, &XdgShellClient::finishInit); | 122 | return m_requestedFrameGeometry.topLeft(); | ||
185 | | ||||
186 | connect(surface(), &SurfaceInterface::committed, this, &XdgShellClient::handleCommitted); | | |||
187 | | ||||
188 | bool needsPlacement = !isInitialPositionSet(); | | |||
189 | | ||||
190 | if (supportsWindowRules()) { | | |||
191 | setupWindowRules(false); | | |||
192 | | ||||
193 | const QRect originalGeometry = frameGeometry(); | | |||
194 | const QRect ruledGeometry = rules()->checkGeometry(originalGeometry, true); | | |||
195 | if (originalGeometry != ruledGeometry) { | | |||
196 | setFrameGeometry(ruledGeometry); | | |||
197 | } | 123 | } | ||
198 | 124 | | |||
199 | maximize(rules()->checkMaximize(maximizeMode(), true)); | 125 | QSize XdgSurfaceClient::requestedSize() const | ||
200 | 126 | { | |||
201 | setDesktop(rules()->checkDesktop(desktop(), true)); | 127 | return m_requestedFrameGeometry.size(); | ||
202 | setDesktopFileName(rules()->checkDesktopFile(desktopFileName(), true).toUtf8()); | | |||
203 | if (rules()->checkMinimize(isMinimized(), true)) { | | |||
204 | minimize(true); // No animation. | | |||
205 | } | 128 | } | ||
206 | setSkipTaskbar(rules()->checkSkipTaskbar(skipTaskbar(), true)); | | |||
207 | setSkipPager(rules()->checkSkipPager(skipPager(), true)); | | |||
208 | setSkipSwitcher(rules()->checkSkipSwitcher(skipSwitcher(), true)); | | |||
209 | setKeepAbove(rules()->checkKeepAbove(keepAbove(), true)); | | |||
210 | setKeepBelow(rules()->checkKeepBelow(keepBelow(), true)); | | |||
211 | setShortcut(rules()->checkShortcut(shortcut().toString(), true)); | | |||
212 | updateColorScheme(); | | |||
213 | 129 | | |||
214 | // Don't place the client if its position is set by a rule. | 130 | QRect XdgSurfaceClient::requestedClientGeometry() const | ||
215 | if (rules()->checkPosition(invalidPoint, true) != invalidPoint) { | 131 | { | ||
216 | needsPlacement = false; | 132 | return m_requestedClientGeometry; | ||
217 | } | 133 | } | ||
218 | 134 | | |||
219 | // Don't place the client if the maximize state is set by a rule. | 135 | /** | ||
220 | if (requestedMaximizeMode() != MaximizeRestore) { | 136 | * \todo When the client is not server-side decorated we probably need to resort to the | ||
221 | needsPlacement = false; | 137 | * bounding geometry, i.e. the rectangle that bounds the main surface and sub-surfaces. | ||
138 | */ | ||||
139 | QRect XdgSurfaceClient::inputGeometry() const | ||||
140 | { | ||||
141 | return isDecorated() ? AbstractClient::inputGeometry() : bufferGeometry(); | ||||
222 | } | 142 | } | ||
223 | 143 | | |||
224 | discardTemporaryRules(); | 144 | QRect XdgSurfaceClient::bufferGeometry() const | ||
225 | RuleBook::self()->discardUsed(this, false); // Remove Apply Now rules. | 145 | { | ||
226 | updateWindowRules(Rules::All); | 146 | return m_bufferGeometry; | ||
227 | } | 147 | } | ||
228 | 148 | | |||
229 | if (isFullScreen()) { | 149 | QSize XdgSurfaceClient::requestedClientSize() const | ||
230 | needsPlacement = false; | 150 | { | ||
151 | return requestedClientGeometry().size(); | ||||
231 | } | 152 | } | ||
232 | 153 | | |||
233 | if (needsPlacement) { | 154 | QRect XdgSurfaceClient::clientGeometry() const | ||
234 | const QRect area = workspace()->clientArea(PlacementArea, Screens::self()->current(), desktop()); | 155 | { | ||
235 | placeIn(area); | 156 | return m_clientGeometry; | ||
236 | } | 157 | } | ||
237 | 158 | | |||
238 | m_requestGeometryBlockCounter--; | 159 | QSize XdgSurfaceClient::clientSize() const | ||
239 | if (m_requestGeometryBlockCounter == 0) { | 160 | { | ||
240 | requestGeometry(m_blockedRequestGeometry); | 161 | return m_clientGeometry.size(); | ||
241 | } | 162 | } | ||
242 | 163 | | |||
243 | m_isInitialized = true; | 164 | QMatrix4x4 XdgSurfaceClient::inputTransformation() const | ||
165 | { | ||||
166 | QMatrix4x4 transformation; | ||||
167 | transformation.translate(-m_bufferGeometry.x(), -m_bufferGeometry.y()); | ||||
168 | return transformation; | ||||
244 | } | 169 | } | ||
245 | 170 | | |||
246 | void XdgShellClient::destroyClient() | 171 | XdgSurfaceConfigure *XdgSurfaceClient::lastAcknowledgedConfigure() const | ||
247 | { | 172 | { | ||
248 | m_closing = true; | 173 | return m_lastAcknowledgedConfigure; | ||
249 | #ifdef KWIN_BUILD_TABBOX | | |||
250 | TabBox::TabBox *tabBox = TabBox::TabBox::self(); | | |||
251 | if (tabBox->isDisplayed() && tabBox->currentClient() == this) { | | |||
252 | tabBox->nextPrev(true); | | |||
253 | } | | |||
254 | #endif | | |||
255 | if (isMoveResize()) { | | |||
256 | leaveMoveResize(); | | |||
257 | } | 174 | } | ||
258 | 175 | | |||
259 | // Replace ShellClient with an instance of Deleted in the stacking order. | 176 | void XdgSurfaceClient::scheduleConfigure() | ||
260 | Deleted *deleted = Deleted::create(this); | 177 | { | ||
261 | emit windowClosed(this, deleted); | 178 | if (!isClosing()) { | ||
262 | 179 | m_configureTimer->start(); | |||
263 | // Remove Force Temporarily rules. | | |||
264 | RuleBook::self()->discardUsed(this, true); | | |||
265 | | ||||
266 | destroyWindowManagementInterface(); | | |||
267 | destroyDecoration(); | | |||
268 | | ||||
269 | StackingUpdatesBlocker blocker(workspace()); | | |||
270 | if (transientFor()) { | | |||
271 | transientFor()->removeTransient(this); | | |||
272 | } | | |||
273 | for (auto it = transients().constBegin(); it != transients().constEnd();) { | | |||
274 | if ((*it)->transientFor() == this) { | | |||
275 | removeTransient(*it); | | |||
276 | it = transients().constBegin(); // restart, just in case something more has changed with the list | | |||
277 | } else { | | |||
278 | ++it; | | |||
279 | } | 180 | } | ||
280 | } | 181 | } | ||
281 | 182 | | |||
282 | waylandServer()->removeClient(this); | 183 | void XdgSurfaceClient::sendConfigure() | ||
184 | { | ||||
185 | XdgSurfaceConfigure *configureEvent = sendRoleConfigure(); | ||||
283 | 186 | | |||
284 | deleted->unrefWindow(); | 187 | if (configureEvent->position != pos()) | ||
188 | configureEvent->presentFields |= XdgSurfaceConfigure::PositionField; | ||||
189 | if (configureEvent->size != size()) | ||||
190 | configureEvent->presentFields |= XdgSurfaceConfigure::SizeField; | ||||
285 | 191 | | |||
286 | m_xdgShellToplevel = nullptr; | 192 | m_configureEvents.append(configureEvent); | ||
287 | m_xdgShellPopup = nullptr; | | |||
288 | deleteClient(this); | | |||
289 | } | 193 | } | ||
290 | 194 | | |||
291 | void XdgShellClient::deleteClient(XdgShellClient *c) | 195 | void XdgSurfaceClient::handleConfigureAcknowledged(quint32 serial) | ||
292 | { | 196 | { | ||
293 | delete c; | 197 | while (!m_configureEvents.isEmpty()) { | ||
198 | if (serial < m_configureEvents.first()->serial) { | ||||
199 | break; | ||||
294 | } | 200 | } | ||
295 | 201 | delete m_lastAcknowledgedConfigure; | |||
296 | QRect XdgShellClient::inputGeometry() const | 202 | m_lastAcknowledgedConfigure = m_configureEvents.takeFirst(); | ||
297 | { | | |||
298 | if (isDecorated()) { | | |||
299 | return AbstractClient::inputGeometry(); | | |||
300 | } | 203 | } | ||
301 | // TODO: What about sub-surfaces sticking outside the main surface? | | |||
302 | return m_bufferGeometry; | | |||
303 | } | 204 | } | ||
304 | 205 | | |||
305 | QRect XdgShellClient::bufferGeometry() const | 206 | void XdgSurfaceClient::handleCommit() | ||
306 | { | 207 | { | ||
307 | return m_bufferGeometry; | 208 | if (!surface()->buffer()) { | ||
209 | return; | ||||
308 | } | 210 | } | ||
309 | 211 | | |||
310 | QStringList XdgShellClient::activities() const | 212 | if (haveNextWindowGeometry()) { | ||
311 | { | 213 | handleNextWindowGeometry(); | ||
312 | // TODO: implement | 214 | resetHaveNextWindowGeometry(); | ||
313 | return QStringList(); | | |||
314 | } | 215 | } | ||
315 | 216 | | |||
316 | QPoint XdgShellClient::clientContentPos() const | 217 | handleRoleCommit(); | ||
317 | { | | |||
318 | return -1 * clientPos(); | | |||
319 | } | | |||
320 | 218 | | |||
321 | QSize XdgShellClient::clientSize() const | 219 | delete m_lastAcknowledgedConfigure; | ||
322 | { | 220 | m_lastAcknowledgedConfigure = nullptr; | ||
323 | const QRect boundingRect = surface()->boundingRect(); | | |||
324 | return m_windowGeometry.size().boundedTo(boundingRect.size()); | | |||
325 | } | | |||
326 | 221 | | |||
327 | QSize XdgShellClient::minSize() const | 222 | internalMap(); | ||
328 | { | 223 | updateDepth(); | ||
329 | if (m_xdgShellToplevel) { | | |||
330 | return rules()->checkMinSize(m_xdgShellToplevel->minimumSize()); | | |||
331 | } | | |||
332 | return QSize(0, 0); | | |||
333 | } | 224 | } | ||
334 | 225 | | |||
335 | QSize XdgShellClient::maxSize() const | 226 | void XdgSurfaceClient::handleRoleCommit() | ||
336 | { | 227 | { | ||
337 | if (m_xdgShellToplevel) { | | |||
338 | return rules()->checkMaxSize(m_xdgShellToplevel->maximumSize()); | | |||
339 | } | | |||
340 | return QSize(INT_MAX, INT_MAX); | | |||
341 | } | 228 | } | ||
342 | 229 | | |||
343 | void XdgShellClient::debug(QDebug &stream) const | 230 | void XdgSurfaceClient::handleNextWindowGeometry() | ||
344 | { | 231 | { | ||
345 | stream.nospace(); | 232 | const QRect boundingGeometry = surface()->boundingRect(); | ||
346 | stream << "\'XdgShellClient:" << surface() << ";WMCLASS:" << resourceClass() << ":" | | |||
347 | << resourceName() << ";Caption:" << caption() << "\'"; | | |||
348 | } | | |||
349 | 233 | | |||
350 | bool XdgShellClient::belongsToDesktop() const | 234 | // The effective window geometry is defined as the intersection of the window geometry | ||
351 | { | 235 | // and the rectangle that bounds the main surface and all of its sub-surfaces. If the | ||
352 | const auto clients = waylandServer()->clients(); | 236 | // client hasn't specified the window geometry, we must fallback to the bounding geometry. | ||
237 | // Note that the xdg-shell spec is not clear about when exactly we have to clamp the | ||||
238 | // window geometry. | ||||
353 | 239 | | |||
354 | return std::any_of(clients.constBegin(), clients.constEnd(), | 240 | m_windowGeometry = m_shellSurface->windowGeometry(); | ||
355 | [this](const AbstractClient *client) { | 241 | if (m_windowGeometry.isValid()) { | ||
356 | if (belongsToSameApplication(client, SameApplicationChecks())) { | 242 | m_windowGeometry &= boundingGeometry; | ||
357 | return client->isDesktop(); | 243 | } else { | ||
358 | } | 244 | m_windowGeometry = boundingGeometry; | ||
359 | return false; | | |||
360 | } | | |||
361 | ); | | |||
362 | } | 245 | } | ||
363 | 246 | | |||
364 | Layer XdgShellClient::layerForDock() const | 247 | if (m_windowGeometry.isEmpty()) { | ||
365 | { | 248 | qCWarning(KWIN_CORE) << "Committed empty window geometry, dealing with a buggy client!"; | ||
366 | if (m_plasmaShellSurface) { | | |||
367 | switch (m_plasmaShellSurface->panelBehavior()) { | | |||
368 | case PlasmaShellSurfaceInterface::PanelBehavior::WindowsCanCover: | | |||
369 | return NormalLayer; | | |||
370 | case PlasmaShellSurfaceInterface::PanelBehavior::AutoHide: | | |||
371 | return AboveLayer; | | |||
372 | case PlasmaShellSurfaceInterface::PanelBehavior::WindowsGoBelow: | | |||
373 | case PlasmaShellSurfaceInterface::PanelBehavior::AlwaysVisible: | | |||
374 | return DockLayer; | | |||
375 | default: | | |||
376 | Q_UNREACHABLE(); | | |||
377 | break; | | |||
378 | } | | |||
379 | } | 249 | } | ||
380 | return AbstractClient::layerForDock(); | 250 | | ||
251 | QRect frameGeometry(pos(), clientSizeToFrameSize(m_windowGeometry.size())); | ||||
252 | | ||||
253 | // We're not done yet. The xdg-shell spec allows clients to attach buffers smaller than | ||||
254 | // we asked. Normally, this is not a big deal, but when the client is being interactively | ||||
255 | // resized, it may cause the window contents to bounce. In order to counter this, we have | ||||
256 | // to "gravitate" the new geometry according to the current move-resize pointer mode so | ||||
257 | // the opposite window corner stays still. | ||||
258 | | ||||
259 | if (isMoveResize()) { | ||||
260 | frameGeometry = adjustMoveResizeGeometry(frameGeometry); | ||||
261 | } else if (lastAcknowledgedConfigure()) { | ||||
262 | XdgSurfaceConfigure *configureEvent = lastAcknowledgedConfigure(); | ||||
263 | | ||||
264 | if (configureEvent->presentFields & XdgSurfaceConfigure::PositionField) | ||||
265 | frameGeometry.moveTopLeft(configureEvent->position); | ||||
381 | } | 266 | } | ||
382 | 267 | | |||
383 | QRect XdgShellClient::transparentRect() const | 268 | updateGeometry(frameGeometry); | ||
384 | { | 269 | | ||
385 | // TODO: implement | 270 | if (isResize()) { | ||
386 | return QRect(); | 271 | performMoveResize(); | ||
272 | } | ||||
387 | } | 273 | } | ||
388 | 274 | | |||
389 | NET::WindowType XdgShellClient::windowType(bool direct, int supported_types) const | 275 | bool XdgSurfaceClient::haveNextWindowGeometry() const | ||
390 | { | 276 | { | ||
391 | // TODO: implement | 277 | return m_haveNextWindowGeometry || m_lastAcknowledgedConfigure; | ||
392 | Q_UNUSED(direct) | | |||
393 | Q_UNUSED(supported_types) | | |||
394 | return m_windowType; | | |||
395 | } | 278 | } | ||
396 | 279 | | |||
397 | double XdgShellClient::opacity() const | 280 | void XdgSurfaceClient::setHaveNextWindowGeometry() | ||
398 | { | 281 | { | ||
399 | return m_opacity; | 282 | m_haveNextWindowGeometry = true; | ||
400 | } | 283 | } | ||
401 | 284 | | |||
402 | void XdgShellClient::setOpacity(double opacity) | 285 | void XdgSurfaceClient::resetHaveNextWindowGeometry() | ||
403 | { | 286 | { | ||
404 | const qreal newOpacity = qBound(0.0, opacity, 1.0); | 287 | m_haveNextWindowGeometry = false; | ||
405 | if (newOpacity == m_opacity) { | | |||
406 | return; | | |||
407 | } | | |||
408 | const qreal oldOpacity = m_opacity; | | |||
409 | m_opacity = newOpacity; | | |||
410 | addRepaintFull(); | | |||
411 | emit opacityChanged(this, oldOpacity); | | |||
412 | } | 288 | } | ||
413 | 289 | | |||
414 | void XdgShellClient::addDamage(const QRegion &damage) | 290 | QRect XdgSurfaceClient::adjustMoveResizeGeometry(const QRect &rect) const | ||
415 | { | 291 | { | ||
416 | const int offsetX = m_bufferGeometry.x() - frameGeometry().x(); | 292 | QRect geometry = rect; | ||
417 | const int offsetY = m_bufferGeometry.y() - frameGeometry().y(); | | |||
418 | repaints_region += damage.translated(offsetX, offsetY); | | |||
419 | 293 | | |||
420 | Toplevel::addDamage(damage); | 294 | switch (moveResizePointerMode()) { | ||
295 | case PositionTopLeft: | ||||
296 | geometry.moveRight(moveResizeGeometry().right()); | ||||
297 | geometry.moveBottom(moveResizeGeometry().bottom()); | ||||
298 | break; | ||||
299 | case PositionTop: | ||||
300 | case PositionTopRight: | ||||
301 | geometry.moveLeft(moveResizeGeometry().left()); | ||||
302 | geometry.moveBottom(moveResizeGeometry().bottom()); | ||||
303 | break; | ||||
304 | case PositionRight: | ||||
305 | case PositionBottomRight: | ||||
306 | case PositionBottom: | ||||
307 | case PositionCenter: | ||||
308 | geometry.moveLeft(moveResizeGeometry().left()); | ||||
309 | geometry.moveTop(moveResizeGeometry().top()); | ||||
310 | break; | ||||
311 | case PositionBottomLeft: | ||||
312 | case PositionLeft: | ||||
313 | geometry.moveRight(moveResizeGeometry().right()); | ||||
314 | geometry.moveTop(moveResizeGeometry().top()); | ||||
315 | break; | ||||
421 | } | 316 | } | ||
422 | 317 | | |||
423 | void XdgShellClient::markAsMapped() | 318 | return geometry; | ||
424 | { | | |||
425 | if (!m_unmapped) { | | |||
426 | return; | | |||
427 | } | 319 | } | ||
428 | 320 | | |||
429 | m_unmapped = false; | 321 | /** | ||
430 | if (!ready_for_painting) { | 322 | * Sets the frame geometry of the XdgSurfaceClient to \a rect. | ||
431 | setReadyForPainting(); | 323 | * | ||
324 | * Because geometry updates are asynchronous on Wayland, there are no any guarantees that | ||||
325 | * the frame geometry will be changed immediately. We may need to send a configure event to | ||||
326 | * the client if the current window geometry size and the requested window geometry size | ||||
327 | * don't match. frameGeometryChanged() will be emitted when the requested frame geometry | ||||
328 | * has been applied. | ||||
329 | * | ||||
330 | * Notice that the client may attach a buffer smaller than the one in the configure event. | ||||
331 | */ | ||||
332 | void XdgSurfaceClient::setFrameGeometry(const QRect &rect, ForceGeometry_t force) | ||||
333 | { | ||||
334 | m_requestedFrameGeometry = rect; | ||||
335 | | ||||
336 | // XdgToplevelClient currently doesn't support shaded clients, but let's stick with | ||||
337 | // what X11Client does. Hopefully, one day we will be able to unify setFrameGeometry() | ||||
338 | // for all AbstractClient subclasses. It's going to be great! | ||||
339 | | ||||
340 | if (isShade()) { | ||||
341 | if (m_requestedFrameGeometry.height() == borderTop() + borderBottom()) { | ||||
342 | qCDebug(KWIN_CORE) << "Passed shaded frame geometry to setFrameGeometry()"; | ||||
432 | } else { | 343 | } else { | ||
433 | addRepaintFull(); | 344 | m_requestedClientGeometry = frameRectToClientRect(m_requestedFrameGeometry); | ||
434 | emit windowShown(this); | 345 | m_requestedFrameGeometry.setHeight(borderTop() + borderBottom()); | ||
435 | } | | |||
436 | if (shouldExposeToWindowManagement()) { | | |||
437 | setupWindowManagementInterface(); | | |||
438 | } | | |||
439 | updateShowOnScreenEdge(); | | |||
440 | } | | |||
441 | | ||||
442 | void XdgShellClient::updateDecoration(bool check_workspace_pos, bool force) | | |||
443 | { | | |||
444 | if (!force && | | |||
445 | ((!isDecorated() && noBorder()) || (isDecorated() && !noBorder()))) | | |||
446 | return; | | |||
447 | QRect oldgeom = frameGeometry(); | | |||
448 | QRect oldClientGeom = oldgeom.adjusted(borderLeft(), borderTop(), -borderRight(), -borderBottom()); | | |||
449 | blockGeometryUpdates(true); | | |||
450 | if (force) | | |||
451 | destroyDecoration(); | | |||
452 | if (!noBorder()) { | | |||
453 | createDecoration(oldgeom); | | |||
454 | } else | | |||
455 | destroyDecoration(); | | |||
456 | if (m_serverDecoration && isDecorated()) { | | |||
457 | m_serverDecoration->setMode(KWaylandServer::ServerSideDecorationManagerInterface::Mode::Server); | | |||
458 | } | 346 | } | ||
459 | if (m_xdgDecoration) { | 347 | } else { | ||
460 | auto mode = isDecorated() || m_userNoBorder ? XdgDecorationInterface::Mode::ServerSide: XdgDecorationInterface::Mode::ClientSide; | 348 | m_requestedClientGeometry = frameRectToClientRect(m_requestedFrameGeometry); | ||
461 | m_xdgDecoration->configure(mode); | | |||
462 | if (m_requestGeometryBlockCounter == 0) { | | |||
463 | m_xdgShellToplevel->configure(xdgSurfaceStates(), m_requestedClientSize); | | |||
464 | } | | |||
465 | } | | |||
466 | updateShadow(); | | |||
467 | if (check_workspace_pos) | | |||
468 | checkWorkspacePosition(oldgeom, -2, oldClientGeom); | | |||
469 | blockGeometryUpdates(false); | | |||
470 | } | 349 | } | ||
471 | 350 | | |||
472 | void XdgShellClient::setFrameGeometry(const QRect &rect, ForceGeometry_t force) | | |||
473 | { | | |||
474 | const QRect newGeometry = rules()->checkGeometry(rect); | | |||
475 | | ||||
476 | if (areGeometryUpdatesBlocked()) { | 351 | if (areGeometryUpdatesBlocked()) { | ||
477 | // when the GeometryUpdateBlocker exits the current geom is passed to setGeometry | 352 | m_frameGeometry = m_requestedFrameGeometry; | ||
478 | // thus we need to set it here. | | |||
479 | m_frameGeometry = newGeometry; | | |||
480 | if (pendingGeometryUpdate() == PendingGeometryForced) { | 353 | if (pendingGeometryUpdate() == PendingGeometryForced) { | ||
481 | // maximum, nothing needed | 354 | return; | ||
482 | } else if (force == ForceGeometrySet) { | 355 | } | ||
356 | if (force == ForceGeometrySet) { | ||||
483 | setPendingGeometryUpdate(PendingGeometryForced); | 357 | setPendingGeometryUpdate(PendingGeometryForced); | ||
484 | } else { | 358 | } else { | ||
485 | setPendingGeometryUpdate(PendingGeometryNormal); | 359 | setPendingGeometryUpdate(PendingGeometryNormal); | ||
486 | } | 360 | } | ||
487 | return; | 361 | return; | ||
488 | } | 362 | } | ||
489 | 363 | | |||
490 | if (pendingGeometryUpdate() != PendingGeometryNone) { | | |||
491 | // reset geometry to the one before blocking, so that we can compare properly | | |||
492 | m_frameGeometry = frameGeometryBeforeUpdateBlocking(); | 364 | m_frameGeometry = frameGeometryBeforeUpdateBlocking(); | ||
493 | } | | |||
494 | 365 | | |||
495 | const QSize requestedClientSize = newGeometry.size() - QSize(borderLeft() + borderRight(), borderTop() + borderBottom()); | 366 | // Notice that the window geometry size of (0, 0) has special meaning to xdg shell clients. | ||
367 | // It basically says "pick whatever size you think is the best, dawg." | ||||
496 | 368 | | |||
497 | if (requestedClientSize == m_windowGeometry.size() && | 369 | if (requestedClientSize() != clientSize()) { | ||
498 | (m_requestedClientSize.isEmpty() || requestedClientSize == m_requestedClientSize)) { | 370 | requestGeometry(requestedFrameGeometry()); | ||
499 | // size didn't change, and we don't need to explicitly request a new size | | |||
500 | doSetGeometry(newGeometry); | | |||
501 | updateMaximizeMode(m_requestedMaximizeMode); | | |||
502 | } else { | 371 | } else { | ||
503 | // size did change, Client needs to provide a new buffer | 372 | updateGeometry(requestedFrameGeometry()); | ||
504 | requestGeometry(newGeometry); | | |||
505 | } | 373 | } | ||
506 | } | 374 | } | ||
507 | 375 | | |||
508 | QRect XdgShellClient::determineBufferGeometry() const | 376 | void XdgSurfaceClient::move(int x, int y, ForceGeometry_t force) | ||
509 | { | 377 | { | ||
510 | // Offset of the main surface relative to the frame rect. | 378 | Q_ASSERT(pendingGeometryUpdate() == PendingGeometryNone || areGeometryUpdatesBlocked()); | ||
511 | const int offsetX = borderLeft() - m_windowGeometry.left(); | 379 | QPoint p(x, y); | ||
512 | const int offsetY = borderTop() - m_windowGeometry.top(); | 380 | if (!areGeometryUpdatesBlocked() && p != rules()->checkPosition(p)) { | ||
381 | qCDebug(KWIN_CORE) << "forced position fail:" << p << ":" << rules()->checkPosition(p); | ||||
382 | } | ||||
383 | m_requestedFrameGeometry.moveTopLeft(p); | ||||
384 | m_requestedClientGeometry.moveTopLeft(framePosToClientPos(p)); | ||||
385 | if (force == NormalGeometrySet && m_frameGeometry.topLeft() == p) { | ||||
386 | return; | ||||
387 | } | ||||
388 | m_frameGeometry.moveTopLeft(m_requestedFrameGeometry.topLeft()); | ||||
389 | if (areGeometryUpdatesBlocked()) { | ||||
390 | if (pendingGeometryUpdate() == PendingGeometryForced) { | ||||
391 | return; | ||||
392 | } | ||||
393 | if (force == ForceGeometrySet) { | ||||
394 | setPendingGeometryUpdate(PendingGeometryForced); | ||||
395 | } else { | ||||
396 | setPendingGeometryUpdate(PendingGeometryNormal); | ||||
397 | } | ||||
398 | return; | ||||
399 | } | ||||
400 | m_clientGeometry.moveTopLeft(m_requestedClientGeometry.topLeft()); | ||||
401 | m_bufferGeometry = frameRectToBufferRect(m_frameGeometry); | ||||
402 | updateWindowRules(Rules::Position); | ||||
403 | screens()->setCurrent(this); | ||||
404 | workspace()->updateStackingOrder(); | ||||
405 | emit frameGeometryChanged(this, frameGeometryBeforeUpdateBlocking()); | ||||
406 | addRepaintDuringGeometryUpdates(); | ||||
407 | updateGeometryBeforeUpdateBlocking(); | ||||
408 | } | ||||
513 | 409 | | |||
514 | QRect bufferGeometry; | 410 | /** | ||
515 | bufferGeometry.setX(x() + offsetX); | 411 | * \internal | ||
516 | bufferGeometry.setY(y() + offsetY); | 412 | * | ||
517 | bufferGeometry.setSize(surface()->size()); | 413 | * Schedules the frame geometry of the XdgSurfaceClient to be updated to \a rect. | ||
414 | * | ||||
415 | * This method is usually called when the current window geometry size and the requested | ||||
416 | * window geometry size don't match. Under hood, this method will schedule a configure | ||||
417 | * event. When the configure event is acknowledged by the client, the frame geometry will | ||||
418 | * be changed to \a rect. | ||||
419 | */ | ||||
420 | void XdgSurfaceClient::requestGeometry(const QRect &rect) | ||||
421 | { | ||||
422 | m_requestedFrameGeometry = rect; | ||||
423 | m_requestedClientGeometry = frameRectToClientRect(rect); | ||||
518 | 424 | | |||
519 | return bufferGeometry; | 425 | // Note that we don't have to send the configure event immediately! | ||
426 | scheduleConfigure(); | ||||
520 | } | 427 | } | ||
521 | 428 | | |||
522 | void XdgShellClient::doSetGeometry(const QRect &rect) | 429 | void XdgSurfaceClient::updateGeometry(const QRect &rect) | ||
523 | { | 430 | { | ||
524 | bool frameGeometryIsChanged = false; | 431 | const QRect oldFrameGeometry = m_frameGeometry; | ||
525 | bool bufferGeometryIsChanged = false; | | |||
526 | 432 | | |||
527 | if (m_frameGeometry != rect) { | | |||
528 | m_frameGeometry = rect; | 433 | m_frameGeometry = rect; | ||
529 | frameGeometryIsChanged = true; | 434 | m_bufferGeometry = frameRectToBufferRect(rect); | ||
530 | } | 435 | m_clientGeometry = frameRectToClientRect(rect); | ||
531 | | ||||
532 | const QRect bufferGeometry = determineBufferGeometry(); | | |||
533 | if (m_bufferGeometry != bufferGeometry) { | | |||
534 | m_bufferGeometry = bufferGeometry; | | |||
535 | bufferGeometryIsChanged = true; | | |||
536 | } | | |||
537 | 436 | | |||
538 | if (!frameGeometryIsChanged && !bufferGeometryIsChanged) { | 437 | if (oldFrameGeometry == m_frameGeometry) | ||
539 | return; | 438 | return; | ||
540 | } | | |||
541 | 439 | | |||
542 | if (m_unmapped && geometryRestore().isEmpty() && !m_frameGeometry.isEmpty()) { | | |||
543 | // use first valid geometry as restore geometry | | |||
544 | setGeometryRestore(m_frameGeometry); | | |||
545 | } | | |||
546 | | ||||
547 | if (frameGeometryIsChanged) { | | |||
548 | if (hasStrut()) { | | |||
549 | workspace()->updateClientArea(); | | |||
550 | } | | |||
551 | updateWindowRules(Rules::Position | Rules::Size); | 440 | updateWindowRules(Rules::Position | Rules::Size); | ||
552 | emit frameGeometryChanged(this, frameGeometryBeforeUpdateBlocking()); | 441 | updateGeometryBeforeUpdateBlocking(); | ||
553 | } | | |||
554 | 442 | | |||
555 | emit geometryShapeChanged(this, frameGeometryBeforeUpdateBlocking()); | 443 | emit frameGeometryChanged(this, oldFrameGeometry); | ||
444 | emit geometryShapeChanged(this, oldFrameGeometry); | ||||
556 | 445 | | |||
557 | addRepaintDuringGeometryUpdates(); | 446 | addRepaintDuringGeometryUpdates(); | ||
558 | updateGeometryBeforeUpdateBlocking(); | 447 | } | ||
559 | 448 | | |||
560 | if (isResize()) { | 449 | /** | ||
561 | performMoveResize(); | 450 | * \internal | ||
451 | * \todo We have to check the current frame geometry in checkWorskpacePosition(). | ||||
452 | * | ||||
453 | * Sets the geometry restore to the first valid frame geometry. This is a HACK! | ||||
454 | * | ||||
455 | * Unfortunately, AbstractClient::checkWorkspacePosition() operates on the geometry restore | ||||
456 | * rather than the current frame geometry, so we have to ensure that it's initialized with | ||||
457 | * some reasonable value even if the client is not maximized or quick tiled. | ||||
458 | */ | ||||
459 | void XdgSurfaceClient::updateGeometryRestoreHack() | ||||
460 | { | ||||
461 | if (isUnmapped() && geometryRestore().isEmpty() && !frameGeometry().isEmpty()) { | ||||
462 | setGeometryRestore(frameGeometry()); | ||||
562 | } | 463 | } | ||
563 | } | 464 | } | ||
564 | 465 | | |||
565 | void XdgShellClient::doMove(int x, int y) | 466 | /** | ||
467 | * \todo The depth value must be set per surface basis. Drop this method when the scene is | ||||
468 | * redesigned for sub-surfaces. | ||||
469 | */ | ||||
470 | void XdgSurfaceClient::updateDepth() | ||||
566 | { | 471 | { | ||
567 | Q_UNUSED(x) | 472 | if (surface()->buffer()->hasAlphaChannel() && !isDesktop()) { | ||
568 | Q_UNUSED(y) | 473 | setDepth(32); | ||
569 | m_bufferGeometry = determineBufferGeometry(); | 474 | } else { | ||
475 | setDepth(24); | ||||
570 | } | 476 | } | ||
571 | | ||||
572 | QByteArray XdgShellClient::windowRole() const | | |||
573 | { | | |||
574 | return QByteArray(); | | |||
575 | } | 477 | } | ||
576 | 478 | | |||
577 | bool XdgShellClient::belongsToSameApplication(const AbstractClient *other, SameApplicationChecks checks) const | 479 | QRect XdgSurfaceClient::frameRectToBufferRect(const QRect &rect) const | ||
578 | { | 480 | { | ||
579 | if (checks.testFlag(SameApplicationCheck::AllowCrossProcesses)) { | 481 | const int left = rect.left() + borderLeft() - m_windowGeometry.left(); | ||
580 | if (other->desktopFileName() == desktopFileName()) { | 482 | const int top = rect.top() + borderTop() - m_windowGeometry.top(); | ||
581 | return true; | 483 | return QRect(QPoint(left, top), surface()->size()); | ||
582 | } | | |||
583 | } | | |||
584 | if (auto s = other->surface()) { | | |||
585 | return s->client() == surface()->client(); | | |||
586 | } | | |||
587 | return false; | | |||
588 | } | 484 | } | ||
589 | 485 | | |||
590 | void XdgShellClient::blockActivityUpdates(bool b) | 486 | /** | ||
487 | * Reimplemented to schedule a repaint when the main surface is damaged. | ||||
488 | * | ||||
489 | * \todo This is actually incorrect. We need to schedule repaints per surface basis, not | ||||
490 | * just for the main surface. Drop this method once the scene is redesigned. | ||||
491 | */ | ||||
492 | void XdgSurfaceClient::addDamage(const QRegion &damage) | ||||
591 | { | 493 | { | ||
592 | Q_UNUSED(b) | 494 | const int offsetX = m_bufferGeometry.x() - m_frameGeometry.x(); | ||
495 | const int offsetY = m_bufferGeometry.y() - m_frameGeometry.y(); | ||||
496 | repaints_region += damage.translated(offsetX, offsetY); | ||||
497 | Toplevel::addDamage(damage); | ||||
593 | } | 498 | } | ||
594 | 499 | | |||
595 | QString XdgShellClient::captionNormal() const | 500 | bool XdgSurfaceClient::isShown(bool shaded_is_shown) const | ||
596 | { | 501 | { | ||
597 | return m_caption; | 502 | Q_UNUSED(shaded_is_shown) | ||
503 | return !isClosing() && !isHidden() && !isMinimized() && !isUnmapped(); | ||||
598 | } | 504 | } | ||
599 | 505 | | |||
600 | QString XdgShellClient::captionSuffix() const | 506 | bool XdgSurfaceClient::isHiddenInternal() const | ||
601 | { | 507 | { | ||
602 | return m_captionSuffix; | 508 | return isHidden() || isUnmapped(); | ||
603 | } | 509 | } | ||
604 | 510 | | |||
605 | void XdgShellClient::updateCaption() | 511 | void XdgSurfaceClient::hideClient(bool hide) | ||
606 | { | 512 | { | ||
607 | const QString oldSuffix = m_captionSuffix; | 513 | if (hide) { | ||
608 | const auto shortcut = shortcutCaptionSuffix(); | 514 | internalHide(); | ||
609 | m_captionSuffix = shortcut; | 515 | } else { | ||
610 | if ((!isSpecialWindow() || isToolbar()) && findClientWithSameCaption()) { | 516 | internalShow(); | ||
611 | int i = 2; | | |||
612 | do { | | |||
613 | m_captionSuffix = shortcut + QLatin1String(" <") + QString::number(i) + QLatin1Char('>'); | | |||
614 | i++; | | |||
615 | } while (findClientWithSameCaption()); | | |||
616 | } | | |||
617 | if (m_captionSuffix != oldSuffix) { | | |||
618 | emit captionChanged(); | | |||
619 | } | 517 | } | ||
620 | } | 518 | } | ||
621 | 519 | | |||
622 | void XdgShellClient::closeWindow() | 520 | bool XdgSurfaceClient::isHidden() const | ||
623 | { | 521 | { | ||
624 | if (m_xdgShellToplevel && isCloseable()) { | 522 | return m_isHidden; | ||
625 | m_xdgShellToplevel->close(); | | |||
626 | ping(PingReason::CloseWindow); | | |||
627 | } | | |||
628 | } | 523 | } | ||
629 | 524 | | |||
630 | AbstractClient *XdgShellClient::findModal(bool allow_itself) | 525 | void XdgSurfaceClient::internalShow() | ||
631 | { | 526 | { | ||
632 | Q_UNUSED(allow_itself) | 527 | if (!isHidden()) { | ||
633 | return nullptr; | 528 | return; | ||
529 | } | ||||
530 | m_isHidden = false; | ||||
531 | addRepaintFull(); | ||||
532 | emit windowShown(this); | ||||
634 | } | 533 | } | ||
635 | 534 | | |||
636 | bool XdgShellClient::isCloseable() const | 535 | void XdgSurfaceClient::internalHide() | ||
637 | { | 536 | { | ||
638 | if (m_windowType == NET::Desktop || m_windowType == NET::Dock) { | 537 | if (isHidden()) { | ||
639 | return false; | 538 | return; | ||
640 | } | 539 | } | ||
641 | if (m_xdgShellToplevel) { | 540 | if (isMoveResize()) { | ||
642 | return true; | 541 | leaveMoveResize(); | ||
643 | } | 542 | } | ||
644 | return false; | 543 | m_isHidden = true; | ||
544 | addWorkspaceRepaint(visibleRect()); | ||||
545 | workspace()->clientHidden(this); | ||||
546 | emit windowHidden(this); | ||||
645 | } | 547 | } | ||
646 | 548 | | |||
647 | bool XdgShellClient::isFullScreen() const | 549 | /** | ||
550 | * \todo We just need to destroy XdgSurfaceClient when the xdg-surface is unmapped. | ||||
551 | */ | ||||
552 | bool XdgSurfaceClient::isUnmapped() const | ||||
648 | { | 553 | { | ||
649 | return m_fullScreen; | 554 | return m_isUnmapped; | ||
650 | } | 555 | } | ||
651 | 556 | | |||
652 | bool XdgShellClient::isMaximizable() const | 557 | /** | ||
558 | * \todo We just need to destroy XdgSurfaceClient when the xdg-surface is unmapped. | ||||
559 | */ | ||||
560 | void XdgSurfaceClient::internalMap() | ||||
653 | { | 561 | { | ||
654 | if (!isResizable()) { | 562 | if (!isUnmapped()) { | ||
655 | return false; | 563 | return; | ||
656 | } | 564 | } | ||
657 | if (rules()->checkMaximize(MaximizeRestore) != MaximizeRestore || rules()->checkMaximize(MaximizeFull) != MaximizeFull) { | 565 | m_isUnmapped = false; | ||
658 | return false; | 566 | if (readyForPainting()) { | ||
567 | addRepaintFull(); | ||||
568 | emit windowShown(this); | ||||
569 | } else { | ||||
570 | setReadyForPainting(); | ||||
659 | } | 571 | } | ||
660 | return true; | 572 | emit windowMapped(); | ||
661 | } | 573 | } | ||
662 | 574 | | |||
663 | bool XdgShellClient::isMinimizable() const | 575 | /** | ||
576 | * \todo We just need to destroy XdgSurfaceClient when the xdg-surface is unmapped. | ||||
577 | */ | ||||
578 | void XdgSurfaceClient::internalUnmap() | ||||
664 | { | 579 | { | ||
665 | if (!rules()->checkMinimize(true)) { | 580 | if (isUnmapped()) { | ||
666 | return false; | 581 | return; | ||
667 | } | 582 | } | ||
668 | return (!m_plasmaShellSurface || m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Normal); | 583 | if (isMoveResize()) { | ||
584 | leaveMoveResize(); | ||||
585 | } | ||||
586 | m_isUnmapped = true; | ||||
587 | m_requestedClientGeometry = QRect(); | ||||
588 | m_lastAcknowledgedConfigure = nullptr; | ||||
589 | m_configureTimer->stop(); | ||||
590 | qDeleteAll(m_configureEvents); | ||||
591 | m_configureEvents.clear(); | ||||
592 | addWorkspaceRepaint(visibleRect()); | ||||
593 | workspace()->clientHidden(this); | ||||
594 | emit windowHidden(this); | ||||
595 | emit windowUnmapped(); | ||||
669 | } | 596 | } | ||
670 | 597 | | |||
671 | bool XdgShellClient::isMovable() const | 598 | bool XdgSurfaceClient::isClosing() const | ||
672 | { | 599 | { | ||
673 | if (isFullScreen()) { | 600 | return m_isClosing; | ||
674 | return false; | | |||
675 | } | | |||
676 | if (rules()->checkPosition(invalidPoint) != invalidPoint) { | | |||
677 | return false; | | |||
678 | } | 601 | } | ||
679 | if (m_plasmaShellSurface) { | 602 | | ||
680 | return m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Normal; | 603 | void XdgSurfaceClient::destroyClient() | ||
681 | } | 604 | { | ||
682 | if (m_xdgShellPopup) { | 605 | m_isClosing = true; | ||
683 | return false; | 606 | m_configureTimer->stop(); | ||
607 | if (isMoveResize()) { | ||||
608 | leaveMoveResize(); | ||||
684 | } | 609 | } | ||
685 | return true; | 610 | cleanTabBox(); | ||
611 | Deleted *deleted = Deleted::create(this); | ||||
612 | emit windowClosed(this, deleted); | ||||
613 | StackingUpdatesBlocker blocker(workspace()); | ||||
614 | RuleBook::self()->discardUsed(this, true); | ||||
615 | destroyWindowManagementInterface(); | ||||
616 | destroyDecoration(); | ||||
617 | cleanGrouping(); | ||||
618 | waylandServer()->removeClient(this); | ||||
619 | deleted->unrefWindow(); | ||||
620 | delete this; | ||||
686 | } | 621 | } | ||
687 | 622 | | |||
688 | bool XdgShellClient::isMovableAcrossScreens() const | 623 | void XdgSurfaceClient::cleanGrouping() | ||
689 | { | 624 | { | ||
690 | if (rules()->checkPosition(invalidPoint) != invalidPoint) { | 625 | if (transientFor()) { | ||
691 | return false; | 626 | transientFor()->removeTransient(this); | ||
692 | } | 627 | } | ||
693 | if (m_plasmaShellSurface) { | 628 | for (auto it = transients().constBegin(); it != transients().constEnd();) { | ||
694 | return m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Normal; | 629 | if ((*it)->transientFor() == this) { | ||
630 | removeTransient(*it); | ||||
631 | it = transients().constBegin(); // restart, just in case something more has changed with the list | ||||
632 | } else { | ||||
633 | ++it; | ||||
695 | } | 634 | } | ||
696 | if (m_xdgShellPopup) { | | |||
697 | return false; | | |||
698 | } | 635 | } | ||
699 | return true; | | |||
700 | } | 636 | } | ||
701 | 637 | | |||
702 | bool XdgShellClient::isResizable() const | 638 | void XdgSurfaceClient::cleanTabBox() | ||
703 | { | 639 | { | ||
704 | if (isFullScreen()) { | 640 | #ifdef KWIN_BUILD_TABBOX | ||
705 | return false; | 641 | TabBox::TabBox *tabBox = TabBox::TabBox::self(); | ||
706 | } | 642 | if (tabBox->isDisplayed() && tabBox->currentClient() == this) { | ||
707 | if (rules()->checkSize(QSize()).isValid()) { | 643 | tabBox->nextPrev(true); | ||
708 | return false; | | |||
709 | } | | |||
710 | if (m_plasmaShellSurface) { | | |||
711 | return m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Normal; | | |||
712 | } | | |||
713 | if (m_xdgShellPopup) { | | |||
714 | return false; | | |||
715 | } | 644 | } | ||
716 | return true; | 645 | #endif | ||
717 | } | 646 | } | ||
718 | 647 | | |||
719 | bool XdgShellClient::isShown(bool shaded_is_shown) const | 648 | XdgToplevelClient::XdgToplevelClient(XdgToplevelInterface *shellSurface) | ||
649 | : XdgSurfaceClient(shellSurface->xdgSurface()) | ||||
650 | , m_shellSurface(shellSurface) | ||||
720 | { | 651 | { | ||
721 | Q_UNUSED(shaded_is_shown) | 652 | setupWindowManagementIntegration(); | ||
722 | return !m_closing && !m_unmapped && !isMinimized() && !m_hidden; | 653 | setupPlasmaShellIntegration(); | ||
723 | } | 654 | setDesktop(VirtualDesktopManager::self()->current()); | ||
724 | 655 | | |||
725 | bool XdgShellClient::isHiddenInternal() const | 656 | if (waylandServer()->inputMethodConnection() == surface()->client()) | ||
657 | m_windowType = NET::OnScreenDisplay; | ||||
658 | | ||||
659 | connect(shellSurface, &XdgToplevelInterface::windowTitleChanged, | ||||
660 | this, &XdgToplevelClient::handleWindowTitleChanged); | ||||
661 | connect(shellSurface, &XdgToplevelInterface::windowClassChanged, | ||||
662 | this, &XdgToplevelClient::handleWindowClassChanged); | ||||
663 | connect(shellSurface, &XdgToplevelInterface::windowMenuRequested, | ||||
664 | this, &XdgToplevelClient::handleWindowMenuRequested); | ||||
665 | connect(shellSurface, &XdgToplevelInterface::moveRequested, | ||||
666 | this, &XdgToplevelClient::handleMoveRequested); | ||||
667 | connect(shellSurface, &XdgToplevelInterface::resizeRequested, | ||||
668 | this, &XdgToplevelClient::handleResizeRequested); | ||||
669 | connect(shellSurface, &XdgToplevelInterface::maximizeRequested, | ||||
670 | this, &XdgToplevelClient::handleMaximizeRequested); | ||||
671 | connect(shellSurface, &XdgToplevelInterface::unmaximizeRequested, | ||||
672 | this, &XdgToplevelClient::handleUnmaximizeRequested); | ||||
673 | connect(shellSurface, &XdgToplevelInterface::fullscreenRequested, | ||||
674 | this, &XdgToplevelClient::handleFullscreenRequested); | ||||
675 | connect(shellSurface, &XdgToplevelInterface::unfullscreenRequested, | ||||
676 | this, &XdgToplevelClient::handleUnfullscreenRequested); | ||||
677 | connect(shellSurface, &XdgToplevelInterface::minimizeRequested, | ||||
678 | this, &XdgToplevelClient::handleMinimizeRequested); | ||||
679 | connect(shellSurface, &XdgToplevelInterface::parentXdgToplevelChanged, | ||||
680 | this, &XdgToplevelClient::handleTransientForChanged); | ||||
681 | connect(shellSurface, &XdgToplevelInterface::initializeRequested, | ||||
682 | this, &XdgToplevelClient::initialize); | ||||
683 | connect(shellSurface, &XdgToplevelInterface::destroyed, | ||||
684 | this, &XdgToplevelClient::destroyClient); | ||||
685 | connect(shellSurface->shell(), &XdgShellInterface::pingTimeout, | ||||
686 | this, &XdgToplevelClient::handlePingTimeout); | ||||
687 | connect(shellSurface->shell(), &XdgShellInterface::pingDelayed, | ||||
688 | this, &XdgToplevelClient::handlePingDelayed); | ||||
689 | connect(shellSurface->shell(), &XdgShellInterface::pongReceived, | ||||
690 | this, &XdgToplevelClient::handlePongReceived); | ||||
691 | | ||||
692 | connect(waylandServer(), &WaylandServer::foreignTransientChanged, | ||||
693 | this, &XdgToplevelClient::handleForeignTransientForChanged); | ||||
694 | | ||||
695 | connect(this, &XdgToplevelClient::clientStartUserMovedResized, | ||||
696 | this, &XdgToplevelClient::scheduleConfigure); | ||||
697 | connect(this, &XdgToplevelClient::clientFinishUserMovedResized, | ||||
698 | this, &XdgToplevelClient::scheduleConfigure); | ||||
699 | } | ||||
700 | | ||||
701 | XdgToplevelClient::~XdgToplevelClient() | ||||
726 | { | 702 | { | ||
727 | return m_unmapped || m_hidden; | | |||
728 | } | 703 | } | ||
729 | 704 | | |||
730 | void XdgShellClient::hideClient(bool hide) | 705 | void XdgToplevelClient::debug(QDebug &stream) const | ||
731 | { | 706 | { | ||
732 | if (m_hidden == hide) { | 707 | stream << this; | ||
733 | return; | | |||
734 | } | | |||
735 | m_hidden = hide; | | |||
736 | if (hide) { | | |||
737 | addWorkspaceRepaint(visibleRect()); | | |||
738 | workspace()->clientHidden(this); | | |||
739 | emit windowHidden(this); | | |||
740 | } else { | | |||
741 | emit windowShown(this); | | |||
742 | } | | |||
743 | } | 708 | } | ||
744 | 709 | | |||
745 | static bool changeMaximizeRecursion = false; | 710 | NET::WindowType XdgToplevelClient::windowType(bool direct, int supported_types) const | ||
746 | void XdgShellClient::changeMaximize(bool horizontal, bool vertical, bool adjust) | | |||
747 | { | 711 | { | ||
748 | if (changeMaximizeRecursion) { | 712 | Q_UNUSED(direct) | ||
749 | return; | 713 | Q_UNUSED(supported_types) | ||
714 | return m_windowType; | ||||
750 | } | 715 | } | ||
751 | 716 | | |||
752 | if (!isResizable()) { | 717 | MaximizeMode XdgToplevelClient::maximizeMode() const | ||
753 | return; | 718 | { | ||
719 | return m_maximizeMode; | ||||
754 | } | 720 | } | ||
755 | 721 | | |||
756 | const QRect clientArea = isElectricBorderMaximizing() ? | 722 | MaximizeMode XdgToplevelClient::requestedMaximizeMode() const | ||
757 | workspace()->clientArea(MaximizeArea, Cursors::self()->mouse()->pos(), desktop()) : | 723 | { | ||
758 | workspace()->clientArea(MaximizeArea, this); | 724 | return m_requestedMaximizeMode; | ||
759 | 725 | } | |||
760 | const MaximizeMode oldMode = m_requestedMaximizeMode; | | |||
761 | const QRect oldGeometry = frameGeometry(); | | |||
762 | 726 | | |||
763 | // 'adjust == true' means to update the size only, e.g. after changing workspace size | 727 | QSize XdgToplevelClient::minSize() const | ||
764 | if (!adjust) { | 728 | { | ||
765 | if (vertical) | 729 | return rules()->checkMinSize(m_shellSurface->minimumSize()); | ||
766 | m_requestedMaximizeMode = MaximizeMode(m_requestedMaximizeMode ^ MaximizeVertical); | | |||
767 | if (horizontal) | | |||
768 | m_requestedMaximizeMode = MaximizeMode(m_requestedMaximizeMode ^ MaximizeHorizontal); | | |||
769 | } | 730 | } | ||
770 | 731 | | |||
771 | m_requestedMaximizeMode = rules()->checkMaximize(m_requestedMaximizeMode); | 732 | QSize XdgToplevelClient::maxSize() const | ||
772 | if (!adjust && m_requestedMaximizeMode == oldMode) { | 733 | { | ||
773 | return; | 734 | return rules()->checkMaxSize(m_shellSurface->maximumSize()); | ||
774 | } | 735 | } | ||
775 | 736 | | |||
776 | StackingUpdatesBlocker blocker(workspace()); | 737 | bool XdgToplevelClient::isFullScreen() const | ||
777 | RequestGeometryBlocker geometryBlocker(this); | 738 | { | ||
739 | return m_isFullScreen; | ||||
740 | } | ||||
778 | 741 | | |||
779 | // call into decoration update borders | 742 | bool XdgToplevelClient::isMovable() const | ||
780 | if (isDecorated() && decoration()->client() && !(options->borderlessMaximizedWindows() && m_requestedMaximizeMode == KWin::MaximizeFull)) { | 743 | { | ||
781 | changeMaximizeRecursion = true; | 744 | if (isFullScreen()) { | ||
782 | const auto c = decoration()->client().toStrongRef(); | 745 | return false; | ||
783 | if ((m_requestedMaximizeMode & MaximizeVertical) != (oldMode & MaximizeVertical)) { | | |||
784 | emit c->maximizedVerticallyChanged(m_requestedMaximizeMode & MaximizeVertical); | | |||
785 | } | 746 | } | ||
786 | if ((m_requestedMaximizeMode & MaximizeHorizontal) != (oldMode & MaximizeHorizontal)) { | 747 | if (isSpecialWindow() && !isSplash() && !isToolbar()) { | ||
787 | emit c->maximizedHorizontallyChanged(m_requestedMaximizeMode & MaximizeHorizontal); | 748 | return false; | ||
788 | } | 749 | } | ||
789 | if ((m_requestedMaximizeMode == MaximizeFull) != (oldMode == MaximizeFull)) { | 750 | if (rules()->checkPosition(invalidPoint) != invalidPoint) { | ||
790 | emit c->maximizedChanged(m_requestedMaximizeMode & MaximizeFull); | 751 | return false; | ||
791 | } | 752 | } | ||
792 | changeMaximizeRecursion = false; | 753 | return true; | ||
793 | } | 754 | } | ||
794 | 755 | | |||
795 | if (options->borderlessMaximizedWindows()) { | 756 | bool XdgToplevelClient::isMovableAcrossScreens() const | ||
796 | // triggers a maximize change. | 757 | { | ||
797 | // The next setNoBorder interation will exit since there's no change but the first recursion pullutes the restore geometry | 758 | if (isSpecialWindow() && !isSplash() && !isToolbar()) { | ||
798 | changeMaximizeRecursion = true; | 759 | return false; | ||
799 | setNoBorder(rules()->checkNoBorder(m_requestedMaximizeMode == MaximizeFull)); | | |||
800 | changeMaximizeRecursion = false; | | |||
801 | } | 760 | } | ||
802 | 761 | if (rules()->checkPosition(invalidPoint) != invalidPoint) { | |||
803 | // Conditional quick tiling exit points | 762 | return false; | ||
804 | const auto oldQuickTileMode = quickTileMode(); | | |||
805 | if (quickTileMode() != QuickTileMode(QuickTileFlag::None)) { | | |||
806 | if (oldMode == MaximizeFull && | | |||
807 | !clientArea.contains(geometryRestore().center())) { | | |||
808 | // Not restoring on the same screen | | |||
809 | // TODO: The following doesn't work for some reason | | |||
810 | //quick_tile_mode = QuickTileNone; // And exit quick tile mode manually | | |||
811 | } else if ((oldMode == MaximizeVertical && m_requestedMaximizeMode == MaximizeRestore) || | | |||
812 | (oldMode == MaximizeFull && m_requestedMaximizeMode == MaximizeHorizontal)) { | | |||
813 | // Modifying geometry of a tiled window | | |||
814 | updateQuickTileMode(QuickTileFlag::None); // Exit quick tile mode without restoring geometry | | |||
815 | } | 763 | } | ||
764 | return true; | ||||
816 | } | 765 | } | ||
817 | 766 | | |||
818 | if (m_requestedMaximizeMode == MaximizeFull) { | 767 | bool XdgToplevelClient::isResizable() const | ||
819 | setGeometryRestore(oldGeometry); | 768 | { | ||
820 | // TODO: Client has more checks | 769 | if (isFullScreen()) { | ||
821 | if (options->electricBorderMaximize()) { | 770 | return false; | ||
822 | updateQuickTileMode(QuickTileFlag::Maximize); | | |||
823 | } else { | | |||
824 | updateQuickTileMode(QuickTileFlag::None); | | |||
825 | } | | |||
826 | if (quickTileMode() != oldQuickTileMode) { | | |||
827 | emit quickTileModeChanged(); | | |||
828 | } | | |||
829 | setFrameGeometry(workspace()->clientArea(MaximizeArea, this)); | | |||
830 | workspace()->raiseClient(this); | | |||
831 | } else { | | |||
832 | if (m_requestedMaximizeMode == MaximizeRestore) { | | |||
833 | updateQuickTileMode(QuickTileFlag::None); | | |||
834 | } | | |||
835 | if (quickTileMode() != oldQuickTileMode) { | | |||
836 | emit quickTileModeChanged(); | | |||
837 | } | 771 | } | ||
838 | 772 | if (isSpecialWindow() || isSplash() || isToolbar()) { | |||
839 | if (geometryRestore().isValid()) { | 773 | return false; | ||
840 | setFrameGeometry(geometryRestore()); | | |||
841 | } else { | | |||
842 | setFrameGeometry(workspace()->clientArea(PlacementArea, this)); | | |||
843 | } | 774 | } | ||
775 | if (rules()->checkSize(QSize()).isValid()) { | ||||
776 | return false; | ||||
844 | } | 777 | } | ||
778 | return true; | ||||
845 | } | 779 | } | ||
846 | 780 | | |||
847 | MaximizeMode XdgShellClient::maximizeMode() const | 781 | bool XdgToplevelClient::isCloseable() const | ||
848 | { | 782 | { | ||
849 | return m_maximizeMode; | 783 | return !isDesktop() && !isDock(); | ||
850 | } | 784 | } | ||
851 | 785 | | |||
852 | MaximizeMode XdgShellClient::requestedMaximizeMode() const | 786 | bool XdgToplevelClient::isFullScreenable() const | ||
853 | { | 787 | { | ||
854 | return m_requestedMaximizeMode; | 788 | if (!rules()->checkFullScreen(true)) { | ||
789 | return false; | ||||
790 | } | ||||
791 | return !isSpecialWindow(); | ||||
855 | } | 792 | } | ||
856 | 793 | | |||
857 | bool XdgShellClient::noBorder() const | 794 | bool XdgToplevelClient::isMaximizable() const | ||
858 | { | 795 | { | ||
859 | if (m_serverDecoration) { | 796 | if (!isResizable()) { | ||
860 | if (m_serverDecoration->mode() == ServerSideDecorationManagerInterface::Mode::Server) { | 797 | return false; | ||
861 | return m_userNoBorder || isFullScreen(); | | |||
862 | } | | |||
863 | } | 798 | } | ||
864 | if (m_xdgDecoration && m_xdgDecoration->requestedMode() != XdgDecorationInterface::Mode::ClientSide) { | 799 | if (rules()->checkMaximize(MaximizeRestore) != MaximizeRestore || | ||
865 | return m_userNoBorder || isFullScreen(); | 800 | rules()->checkMaximize(MaximizeFull) != MaximizeFull) { | ||
801 | return false; | ||||
866 | } | 802 | } | ||
867 | return true; | 803 | return true; | ||
868 | } | 804 | } | ||
869 | 805 | | |||
870 | bool XdgShellClient::isFullScreenable() const | 806 | bool XdgToplevelClient::isMinimizable() const | ||
871 | { | 807 | { | ||
872 | if (!rules()->checkFullScreen(true)) { | 808 | if (isSpecialWindow() && !isTransient()) { | ||
873 | return false; | 809 | return false; | ||
874 | } | 810 | } | ||
875 | return !isSpecialWindow(); | 811 | if (!rules()->checkMinimize(true)) { | ||
812 | return false; | ||||
813 | } | ||||
814 | return true; | ||||
876 | } | 815 | } | ||
877 | 816 | | |||
878 | void XdgShellClient::setFullScreen(bool set, bool user) | 817 | bool XdgToplevelClient::isTransient() const | ||
879 | { | 818 | { | ||
880 | set = rules()->checkFullScreen(set); | 819 | return m_isTransient; | ||
820 | } | ||||
881 | 821 | | |||
882 | const bool wasFullscreen = isFullScreen(); | 822 | bool XdgToplevelClient::userCanSetFullScreen() const | ||
883 | if (wasFullscreen == set) { | 823 | { | ||
884 | return; | 824 | return true; | ||
885 | } | 825 | } | ||
886 | if (isSpecialWindow()) { | 826 | | ||
887 | return; | 827 | bool XdgToplevelClient::userCanSetNoBorder() const | ||
828 | { | ||||
829 | if (m_serverDecoration) { | ||||
830 | switch (m_serverDecoration->mode()) { | ||||
831 | case ServerSideDecorationManagerInterface::Mode::Server: | ||||
832 | return !isFullScreen() && !isShade(); | ||||
833 | case ServerSideDecorationManagerInterface::Mode::Client: | ||||
834 | case ServerSideDecorationManagerInterface::Mode::None: | ||||
835 | return false; | ||||
888 | } | 836 | } | ||
889 | if (user && !userCanSetFullScreen()) { | | |||
890 | return; | | |||
891 | } | 837 | } | ||
892 | 838 | if (m_xdgDecoration) { | |||
893 | if (wasFullscreen) { | 839 | switch (m_xdgDecoration->preferredMode()) { | ||
894 | workspace()->updateFocusMousePosition(Cursors::self()->mouse()->pos()); // may cause leave event | 840 | case XdgToplevelDecorationV1Interface::Mode::Server: | ||
895 | } else { | 841 | case XdgToplevelDecorationV1Interface::Mode::Undefined: | ||
896 | m_geomFsRestore = frameGeometry(); | 842 | return !isFullScreen() && !isShade(); | ||
843 | case XdgToplevelDecorationV1Interface::Mode::Client: | ||||
844 | return false; | ||||
897 | } | 845 | } | ||
898 | m_fullScreen = set; | | |||
899 | | ||||
900 | if (set) { | | |||
901 | workspace()->raiseClient(this); | | |||
902 | } | 846 | } | ||
903 | RequestGeometryBlocker requestBlocker(this); | 847 | return false; | ||
904 | StackingUpdatesBlocker blocker1(workspace()); | 848 | } | ||
905 | GeometryUpdatesBlocker blocker2(this); | | |||
906 | | ||||
907 | workspace()->updateClientLayer(this); // active fullscreens get different layer | | |||
908 | updateDecoration(false, false); | | |||
909 | 849 | | |||
910 | if (set) { | 850 | bool XdgToplevelClient::noBorder() const | ||
911 | setFrameGeometry(workspace()->clientArea(FullScreenArea, this)); | 851 | { | ||
912 | } else { | 852 | if (m_serverDecoration) { | ||
913 | if (m_geomFsRestore.isValid()) { | 853 | switch (m_serverDecoration->mode()) { | ||
914 | int currentScreen = screen(); | 854 | case ServerSideDecorationManagerInterface::Mode::Server: | ||
915 | setFrameGeometry(QRect(m_geomFsRestore.topLeft(), constrainFrameSize(m_geomFsRestore.size()))); | 855 | return m_userNoBorder || isFullScreen(); | ||
916 | if( currentScreen != screen()) | 856 | case ServerSideDecorationManagerInterface::Mode::Client: | ||
917 | workspace()->sendClientToScreen( this, currentScreen ); | 857 | case ServerSideDecorationManagerInterface::Mode::None: | ||
918 | } else { | 858 | return true; | ||
919 | // this can happen when the window was first shown already fullscreen, | 859 | } | ||
920 | // so let the client set the size by itself | | |||
921 | setFrameGeometry(QRect(workspace()->clientArea(PlacementArea, this).topLeft(), QSize(0, 0))); | | |||
922 | } | 860 | } | ||
861 | if (m_xdgDecoration) { | ||||
862 | switch (m_xdgDecoration->preferredMode()) { | ||||
863 | case XdgToplevelDecorationV1Interface::Mode::Server: | ||||
864 | case XdgToplevelDecorationV1Interface::Mode::Undefined: | ||||
865 | return m_userNoBorder || isFullScreen(); | ||||
866 | case XdgToplevelDecorationV1Interface::Mode::Client: | ||||
867 | return true; | ||||
923 | } | 868 | } | ||
924 | 869 | } | |||
925 | updateWindowRules(Rules::Fullscreen|Rules::Position|Rules::Size); | 870 | return true; | ||
926 | emit fullScreenChanged(); | | |||
927 | } | 871 | } | ||
928 | 872 | | |||
929 | void XdgShellClient::setNoBorder(bool set) | 873 | void XdgToplevelClient::setNoBorder(bool set) | ||
930 | { | 874 | { | ||
931 | if (!userCanSetNoBorder()) { | 875 | if (!userCanSetNoBorder()) { | ||
932 | return; | 876 | return; | ||
933 | } | 877 | } | ||
934 | set = rules()->checkNoBorder(set); | 878 | set = rules()->checkNoBorder(set); | ||
935 | if (m_userNoBorder == set) { | 879 | if (m_userNoBorder == set) { | ||
936 | return; | 880 | return; | ||
937 | } | 881 | } | ||
938 | m_userNoBorder = set; | 882 | m_userNoBorder = set; | ||
939 | updateDecoration(true, false); | 883 | updateDecoration(true, false); | ||
940 | updateWindowRules(Rules::NoBorder); | 884 | updateWindowRules(Rules::NoBorder); | ||
941 | } | 885 | } | ||
942 | 886 | | |||
943 | void XdgShellClient::setOnAllActivities(bool set) | 887 | void XdgToplevelClient::updateDecoration(bool check_workspace_pos, bool force) | ||
944 | { | 888 | { | ||
945 | Q_UNUSED(set) | 889 | if (!force && ((!isDecorated() && noBorder()) || (isDecorated() && !noBorder()))) { | ||
890 | return; | ||||
946 | } | 891 | } | ||
947 | 892 | const QRect oldFrameGeometry = frameGeometry(); | |||
948 | void XdgShellClient::takeFocus() | 893 | const QRect oldClientGeometry = clientGeometry(); | ||
949 | { | 894 | blockGeometryUpdates(true); | ||
950 | if (rules()->checkAcceptFocus(wantsInput())) { | 895 | if (force) { | ||
951 | if (m_xdgShellToplevel) { | 896 | destroyDecoration(); | ||
952 | ping(PingReason::FocusWindow); | | |||
953 | } | 897 | } | ||
954 | setActive(true); | 898 | if (!noBorder()) { | ||
899 | createDecoration(oldFrameGeometry); | ||||
900 | } else { | ||||
901 | destroyDecoration(); | ||||
955 | } | 902 | } | ||
956 | 903 | if (m_serverDecoration && isDecorated()) { | |||
957 | if (!keepAbove() && !isOnScreenDisplay() && !belongsToDesktop()) { | 904 | m_serverDecoration->setMode(ServerSideDecorationManagerInterface::Mode::Server); | ||
958 | workspace()->setShowingDesktop(false); | 905 | } | ||
906 | if (m_xdgDecoration) { | ||||
907 | if (isDecorated() || m_userNoBorder) { | ||||
908 | m_xdgDecoration->sendConfigure(XdgToplevelDecorationV1Interface::Mode::Server); | ||||
909 | } else { | ||||
910 | m_xdgDecoration->sendConfigure(XdgToplevelDecorationV1Interface::Mode::Client); | ||||
911 | } | ||||
912 | scheduleConfigure(); | ||||
913 | } | ||||
914 | updateShadow(); | ||||
915 | if (check_workspace_pos) { | ||||
916 | checkWorkspacePosition(oldFrameGeometry, -2, oldClientGeometry); | ||||
959 | } | 917 | } | ||
918 | blockGeometryUpdates(false); | ||||
960 | } | 919 | } | ||
961 | 920 | | |||
962 | void XdgShellClient::doSetActive() | 921 | bool XdgToplevelClient::supportsWindowRules() const | ||
963 | { | 922 | { | ||
964 | if (!isActive()) { | 923 | return !m_plasmaShellSurface; | ||
965 | return; | | |||
966 | } | | |||
967 | StackingUpdatesBlocker blocker(workspace()); | | |||
968 | workspace()->focusToNull(); | | |||
969 | } | 924 | } | ||
970 | 925 | | |||
971 | bool XdgShellClient::userCanSetFullScreen() const | 926 | bool XdgToplevelClient::hasStrut() const | ||
972 | { | 927 | { | ||
973 | if (m_xdgShellToplevel) { | 928 | if (!isShown(true)) { | ||
974 | return true; | 929 | return false; | ||
930 | } | ||||
931 | if (!m_plasmaShellSurface) { | ||||
932 | return false; | ||||
975 | } | 933 | } | ||
934 | if (m_plasmaShellSurface->role() != PlasmaShellSurfaceInterface::Role::Panel) { | ||||
976 | return false; | 935 | return false; | ||
977 | } | 936 | } | ||
937 | return m_plasmaShellSurface->panelBehavior() == PlasmaShellSurfaceInterface::PanelBehavior::AlwaysVisible; | ||||
938 | } | ||||
978 | 939 | | |||
979 | bool XdgShellClient::userCanSetNoBorder() const | 940 | void XdgToplevelClient::showOnScreenEdge() | ||
980 | { | 941 | { | ||
981 | if (m_serverDecoration && m_serverDecoration->mode() == ServerSideDecorationManagerInterface::Mode::Server) { | 942 | if (!m_plasmaShellSurface || isUnmapped()) { | ||
982 | return !isFullScreen() && !isShade(); | 943 | return; | ||
983 | } | 944 | } | ||
984 | if (m_xdgDecoration && m_xdgDecoration->requestedMode() != XdgDecorationInterface::Mode::ClientSide) { | 945 | hideClient(false); | ||
985 | return !isFullScreen() && !isShade(); | 946 | workspace()->raiseClient(this); | ||
947 | if (m_plasmaShellSurface->panelBehavior() == PlasmaShellSurfaceInterface::PanelBehavior::AutoHide) { | ||||
948 | m_plasmaShellSurface->showAutoHidingPanel(); | ||||
986 | } | 949 | } | ||
987 | return false; | | |||
988 | } | 950 | } | ||
989 | 951 | | |||
990 | bool XdgShellClient::wantsInput() const | 952 | bool XdgToplevelClient::isInitialPositionSet() const | ||
991 | { | 953 | { | ||
992 | return rules()->checkAcceptFocus(acceptsFocus()); | 954 | return m_plasmaShellSurface ? m_plasmaShellSurface->isPositionSet() : false; | ||
993 | } | 955 | } | ||
994 | 956 | | |||
995 | bool XdgShellClient::acceptsFocus() const | 957 | void XdgToplevelClient::closeWindow() | ||
996 | { | 958 | { | ||
997 | if (waylandServer()->inputMethodConnection() == surface()->client()) { | 959 | if (isCloseable()) { | ||
998 | return false; | 960 | sendPing(PingReason::CloseWindow); | ||
961 | m_shellSurface->sendClose(); | ||||
999 | } | 962 | } | ||
1000 | if (m_plasmaShellSurface) { | | |||
1001 | if (m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::OnScreenDisplay || | | |||
1002 | m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::ToolTip) { | | |||
1003 | return false; | | |||
1004 | } | 963 | } | ||
1005 | 964 | | |||
1006 | if (m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Notification || | 965 | XdgSurfaceConfigure *XdgToplevelClient::sendRoleConfigure() const | ||
1007 | m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::CriticalNotification) { | 966 | { | ||
1008 | return m_plasmaShellSurface->panelTakesFocus(); | 967 | XdgToplevelInterface::States xdgStates; | ||
1009 | } | 968 | | ||
969 | if (isActive()) { | ||||
970 | xdgStates |= XdgToplevelInterface::State::Activated; | ||||
1010 | } | 971 | } | ||
1011 | if (m_closing) { | 972 | if (isResize()) { | ||
1012 | // a closing window does not accept focus | 973 | xdgStates |= XdgToplevelInterface::State::Resizing; | ||
1013 | return false; | | |||
1014 | } | 974 | } | ||
1015 | if (m_unmapped) { | 975 | if (requestedMaximizeMode() & MaximizeHorizontal) { | ||
1016 | // an unmapped window does not accept focus | 976 | xdgStates |= XdgToplevelInterface::State::MaximizedHorizontal; | ||
1017 | return false; | | |||
1018 | } | 977 | } | ||
1019 | if (m_xdgShellToplevel) { | 978 | if (requestedMaximizeMode() & MaximizeVertical) { | ||
1020 | // TODO: proper | 979 | xdgStates |= XdgToplevelInterface::State::MaximizedVertical; | ||
1021 | return true; | | |||
1022 | } | 980 | } | ||
1023 | return false; | 981 | if (isFullScreen()) { | ||
982 | xdgStates |= XdgToplevelInterface::State::FullScreen; | ||||
1024 | } | 983 | } | ||
1025 | 984 | | |||
1026 | void XdgShellClient::createWindowId() | 985 | const quint32 serial = m_shellSurface->sendConfigure(requestedClientSize(), xdgStates); | ||
1027 | { | | |||
1028 | m_windowId = waylandServer()->createWindowId(surface()); | | |||
1029 | } | | |||
1030 | 986 | | |||
1031 | pid_t XdgShellClient::pid() const | 987 | XdgToplevelConfigure *configureEvent = new XdgToplevelConfigure(); | ||
1032 | { | 988 | configureEvent->position = requestedPos(); | ||
1033 | return surface()->client()->processId(); | 989 | configureEvent->size = requestedSize(); | ||
1034 | } | 990 | configureEvent->states = xdgStates; | ||
991 | configureEvent->serial = serial; | ||||
1035 | 992 | | |||
1036 | bool XdgShellClient::isLockScreen() const | 993 | return configureEvent; | ||
1037 | { | | |||
1038 | return surface()->client() == waylandServer()->screenLockerClientConnection(); | | |||
1039 | } | 994 | } | ||
1040 | 995 | | |||
1041 | bool XdgShellClient::isInputMethod() const | 996 | void XdgToplevelClient::handleRoleCommit() | ||
1042 | { | 997 | { | ||
1043 | return surface()->client() == waylandServer()->inputMethodConnection(); | 998 | auto configureEvent = static_cast<XdgToplevelConfigure *>(lastAcknowledgedConfigure()); | ||
999 | if (configureEvent) | ||||
1000 | handleStatesAcknowledged(configureEvent->states); | ||||
1044 | } | 1001 | } | ||
1045 | 1002 | | |||
1046 | void XdgShellClient::requestGeometry(const QRect &rect) | 1003 | void XdgToplevelClient::doMinimize() | ||
1047 | { | 1004 | { | ||
1048 | if (m_requestGeometryBlockCounter != 0) { | 1005 | if (isMinimized()) { | ||
1049 | m_blockedRequestGeometry = rect; | 1006 | workspace()->clientHidden(this); | ||
1050 | return; | | |||
1051 | } | | |||
1052 | | ||||
1053 | QSize size; | | |||
1054 | if (rect.isValid()) { | | |||
1055 | size = rect.size() - QSize(borderLeft() + borderRight(), borderTop() + borderBottom()); | | |||
1056 | } else { | 1007 | } else { | ||
1057 | size = QSize(0, 0); | 1008 | emit windowShown(this); | ||
1058 | } | | |||
1059 | m_requestedClientSize = size; | | |||
1060 | | ||||
1061 | quint64 serialId = 0; | | |||
1062 | | ||||
1063 | if (m_xdgShellToplevel) { | | |||
1064 | serialId = m_xdgShellToplevel->configure(xdgSurfaceStates(), size); | | |||
1065 | } | | |||
1066 | if (m_xdgShellPopup) { | | |||
1067 | auto parent = transientFor(); | | |||
1068 | if (parent) { | | |||
1069 | const QPoint globalClientContentPos = parent->frameGeometry().topLeft() + parent->clientPos(); | | |||
1070 | const QPoint relativeOffset = rect.topLeft() - globalClientContentPos; | | |||
1071 | serialId = m_xdgShellPopup->configure(QRect(relativeOffset, size)); | | |||
1072 | } | | |||
1073 | } | 1009 | } | ||
1074 | 1010 | workspace()->updateMinimizedOfTransients(this); | |||
1075 | if (rect.isValid()) { //if there's no requested size, then there's implicity no positional information worth using | | |||
1076 | PendingConfigureRequest configureRequest; | | |||
1077 | configureRequest.serialId = serialId; | | |||
1078 | configureRequest.positionAfterResize = rect.topLeft(); | | |||
1079 | configureRequest.maximizeMode = m_requestedMaximizeMode; | | |||
1080 | m_pendingConfigureRequests.append(configureRequest); | | |||
1081 | } | 1011 | } | ||
1082 | 1012 | | |||
1083 | m_blockedRequestGeometry = QRect(); | 1013 | void XdgToplevelClient::doResizeSync() | ||
1014 | { | ||||
1015 | requestGeometry(moveResizeGeometry()); | ||||
1084 | } | 1016 | } | ||
1085 | 1017 | | |||
1086 | void XdgShellClient::updatePendingGeometry() | 1018 | void XdgToplevelClient::doSetActive() | ||
1087 | { | 1019 | { | ||
1088 | QPoint position = pos(); | 1020 | scheduleConfigure(); | ||
1089 | MaximizeMode maximizeMode = m_maximizeMode; | 1021 | if (!isActive()) { | ||
1090 | for (auto it = m_pendingConfigureRequests.begin(); it != m_pendingConfigureRequests.end(); it++) { | 1022 | return; | ||
1091 | if (it->serialId > m_lastAckedConfigureRequest) { | | |||
1092 | //this serial is not acked yet, therefore we know all future serials are not | | |||
1093 | break; | | |||
1094 | } | 1023 | } | ||
1095 | if (it->serialId == m_lastAckedConfigureRequest) { | 1024 | StackingUpdatesBlocker blocker(workspace()); | ||
1096 | if (position != it->positionAfterResize) { | 1025 | workspace()->focusToNull(); | ||
1097 | addLayerRepaint(frameGeometry()); | | |||
1098 | } | 1026 | } | ||
1099 | position = it->positionAfterResize; | | |||
1100 | maximizeMode = it->maximizeMode; | | |||
1101 | 1027 | | |||
1102 | m_pendingConfigureRequests.erase(m_pendingConfigureRequests.begin(), ++it); | 1028 | void XdgToplevelClient::takeFocus() | ||
1103 | break; | 1029 | { | ||
1104 | } | 1030 | if (wantsInput()) { | ||
1105 | //else serialId < m_lastAckedConfigureRequest and the state is now irrelevant and can be ignored | 1031 | sendPing(PingReason::FocusWindow); | ||
1106 | } | 1032 | setActive(true); | ||
1107 | QRect geometry = QRect(position, adjustedSize()); | | |||
1108 | if (isMove()) { | | |||
1109 | geometry = adjustMoveGeometry(geometry); | | |||
1110 | } | 1033 | } | ||
1111 | if (isResize()) { | 1034 | if (!keepAbove() && !isOnScreenDisplay() && !belongsToDesktop()) { | ||
1112 | geometry = adjustResizeGeometry(geometry); | 1035 | workspace()->setShowingDesktop(false); | ||
1113 | } | 1036 | } | ||
1114 | doSetGeometry(geometry); | | |||
1115 | updateMaximizeMode(maximizeMode); | | |||
1116 | } | 1037 | } | ||
1117 | 1038 | | |||
1118 | void XdgShellClient::handleConfigureAcknowledged(quint32 serial) | 1039 | bool XdgToplevelClient::wantsInput() const | ||
1119 | { | 1040 | { | ||
1120 | m_lastAckedConfigureRequest = serial; | 1041 | return rules()->checkAcceptFocus(acceptsFocus()); | ||
1121 | } | 1042 | } | ||
1122 | 1043 | | |||
1123 | void XdgShellClient::handleTransientForChanged() | 1044 | bool XdgToplevelClient::dockWantsInput() const | ||
1124 | { | 1045 | { | ||
1125 | SurfaceInterface *transientSurface = nullptr; | 1046 | if (m_plasmaShellSurface) { | ||
1126 | if (m_xdgShellToplevel) { | 1047 | if (m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Panel) { | ||
1127 | if (auto transient = m_xdgShellToplevel->transientFor().data()) { | 1048 | return m_plasmaShellSurface->panelTakesFocus(); | ||
1128 | transientSurface = transient->surface(); | | |||
1129 | } | 1049 | } | ||
1130 | } | 1050 | } | ||
1131 | if (m_xdgShellPopup) { | 1051 | return false; | ||
1132 | transientSurface = m_xdgShellPopup->transientFor().data(); | | |||
1133 | } | 1052 | } | ||
1134 | if (!transientSurface) { | 1053 | | ||
1135 | transientSurface = waylandServer()->findForeignTransientForSurface(surface()); | 1054 | bool XdgToplevelClient::acceptsFocus() const | ||
1055 | { | ||||
1056 | if (isInputMethod()) { | ||||
1057 | return false; | ||||
1136 | } | 1058 | } | ||
1137 | AbstractClient *transientClient = waylandServer()->findClient(transientSurface); | 1059 | if (m_plasmaShellSurface) { | ||
1138 | if (transientClient != transientFor()) { | 1060 | if (m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::OnScreenDisplay || | ||
1139 | // Remove from main client. | 1061 | m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::ToolTip) { | ||
1140 | if (transientFor()) { | 1062 | return false; | ||
1141 | transientFor()->removeTransient(this); | | |||
1142 | } | 1063 | } | ||
1143 | setTransientFor(transientClient); | 1064 | if (m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Notification || | ||
1144 | if (transientClient) { | 1065 | m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::CriticalNotification) { | ||
1145 | transientClient->addTransient(this); | 1066 | return m_plasmaShellSurface->panelTakesFocus(); | ||
1146 | } | 1067 | } | ||
1147 | } | 1068 | } | ||
1148 | m_transient = (transientSurface != nullptr); | 1069 | return !isClosing() && !isUnmapped(); | ||
1149 | } | 1070 | } | ||
1150 | 1071 | | |||
1151 | void XdgShellClient::handleWindowClassChanged(const QByteArray &windowClass) | 1072 | Layer XdgToplevelClient::layerForDock() const | ||
1152 | { | 1073 | { | ||
1153 | setResourceClass(resourceName(), windowClass); | 1074 | if (m_plasmaShellSurface) { | ||
1154 | if (m_isInitialized && supportsWindowRules()) { | 1075 | switch (m_plasmaShellSurface->panelBehavior()) { | ||
1155 | setupWindowRules(true); | 1076 | case PlasmaShellSurfaceInterface::PanelBehavior::WindowsCanCover: | ||
1156 | applyWindowRules(); | 1077 | return NormalLayer; | ||
1078 | case PlasmaShellSurfaceInterface::PanelBehavior::AutoHide: | ||||
1079 | return AboveLayer; | ||||
1080 | case PlasmaShellSurfaceInterface::PanelBehavior::WindowsGoBelow: | ||||
1081 | case PlasmaShellSurfaceInterface::PanelBehavior::AlwaysVisible: | ||||
1082 | return DockLayer; | ||||
1083 | default: | ||||
1084 | Q_UNREACHABLE(); | ||||
1085 | break; | ||||
1157 | } | 1086 | } | ||
1158 | setDesktopFileName(windowClass); | 1087 | } | ||
1088 | return AbstractClient::layerForDock(); | ||||
1159 | } | 1089 | } | ||
1160 | 1090 | | |||
1161 | void XdgShellClient::handleWindowGeometryChanged(const QRect &windowGeometry) | 1091 | void XdgToplevelClient::handleWindowTitleChanged() | ||
1162 | { | 1092 | { | ||
1163 | m_windowGeometry = windowGeometry; | 1093 | setCaption(m_shellSurface->windowTitle()); | ||
1164 | m_hasWindowGeometry = true; | | |||
1165 | } | 1094 | } | ||
1166 | 1095 | | |||
1167 | void XdgShellClient::handleWindowTitleChanged(const QString &title) | 1096 | void XdgToplevelClient::handleWindowClassChanged() | ||
1168 | { | 1097 | { | ||
1169 | const QString oldSuffix = m_captionSuffix; | 1098 | const QByteArray applicationId = m_shellSurface->windowClass().toUtf8(); | ||
1170 | m_caption = title.simplified(); | 1099 | setResourceClass(resourceName(), applicationId); | ||
1171 | updateCaption(); | 1100 | if (m_isInitialized && supportsWindowRules()) { | ||
1172 | if (m_captionSuffix == oldSuffix) { | 1101 | evaluateWindowRules(); | ||
1173 | // Don't emit caption change twice it already got emitted by the changing suffix. | 1102 | } | ||
1174 | emit captionChanged(); | 1103 | setDesktopFileName(applicationId); | ||
1175 | } | 1104 | } | ||
1105 | | ||||
1106 | void XdgToplevelClient::handleWindowMenuRequested(SeatInterface *seat, const QPoint &surfacePos, | ||||
1107 | quint32 serial) | ||||
1108 | { | ||||
1109 | Q_UNUSED(seat) | ||||
1110 | Q_UNUSED(serial) | ||||
1111 | performMouseCommand(Options::MouseOperationsMenu, pos() + surfacePos); | ||||
1176 | } | 1112 | } | ||
1177 | 1113 | | |||
1178 | void XdgShellClient::handleMoveRequested(SeatInterface *seat, quint32 serial) | 1114 | void XdgToplevelClient::handleMoveRequested(SeatInterface *seat, quint32 serial) | ||
1179 | { | 1115 | { | ||
1180 | // FIXME: Check the seat and serial. | | |||
1181 | Q_UNUSED(seat) | 1116 | Q_UNUSED(seat) | ||
1182 | Q_UNUSED(serial) | 1117 | Q_UNUSED(serial) | ||
1183 | performMouseCommand(Options::MouseMove, Cursors::self()->mouse()->pos()); | 1118 | performMouseCommand(Options::MouseMove, Cursors::self()->mouse()->pos()); | ||
1184 | } | 1119 | } | ||
1185 | 1120 | | |||
1186 | void XdgShellClient::handleResizeRequested(SeatInterface *seat, quint32 serial, Qt::Edges edges) | 1121 | void XdgToplevelClient::handleResizeRequested(SeatInterface *seat, Qt::Edges edges, quint32 serial) | ||
1187 | { | 1122 | { | ||
1188 | // FIXME: Check the seat and serial. | | |||
1189 | Q_UNUSED(seat) | 1123 | Q_UNUSED(seat) | ||
1190 | Q_UNUSED(serial) | 1124 | Q_UNUSED(serial) | ||
1191 | if (!isResizable() || isShade()) { | 1125 | if (!isResizable() || isShade()) { | ||
1192 | return; | 1126 | return; | ||
1193 | } | 1127 | } | ||
1194 | if (isMoveResize()) { | 1128 | if (isMoveResize()) { | ||
1195 | finishMoveResize(false); | 1129 | finishMoveResize(false); | ||
1196 | } | 1130 | } | ||
Show All 17 Lines | |||||
1214 | }; | 1148 | }; | ||
1215 | setMoveResizePointerMode(toPosition()); | 1149 | setMoveResizePointerMode(toPosition()); | ||
1216 | if (!startMoveResize()) { | 1150 | if (!startMoveResize()) { | ||
1217 | setMoveResizePointerButtonDown(false); | 1151 | setMoveResizePointerButtonDown(false); | ||
1218 | } | 1152 | } | ||
1219 | updateCursor(); | 1153 | updateCursor(); | ||
1220 | } | 1154 | } | ||
1221 | 1155 | | |||
1222 | void XdgShellClient::handleMinimizeRequested() | 1156 | void XdgToplevelClient::handleStatesAcknowledged(const XdgToplevelInterface::States &states) | ||
1157 | { | ||||
1158 | const XdgToplevelInterface::States delta = m_lastAcknowledgedStates ^ states; | ||||
1159 | | ||||
1160 | if (delta & XdgToplevelInterface::State::Maximized) { | ||||
1161 | MaximizeMode maximizeMode = MaximizeRestore; | ||||
1162 | if (states & XdgToplevelInterface::State::MaximizedHorizontal) { | ||||
1163 | maximizeMode = MaximizeMode(maximizeMode | MaximizeHorizontal); | ||||
1164 | } | ||||
1165 | if (states & XdgToplevelInterface::State::MaximizedVertical) { | ||||
1166 | maximizeMode = MaximizeMode(maximizeMode | MaximizeVertical); | ||||
1167 | } | ||||
1168 | updateMaximizeMode(maximizeMode); | ||||
1169 | } | ||||
1170 | if (delta & XdgToplevelInterface::State::FullScreen) { | ||||
1171 | updateFullScreenMode(states & XdgToplevelInterface::State::FullScreen); | ||||
1172 | } | ||||
1173 | | ||||
1174 | m_lastAcknowledgedStates = states; | ||||
1175 | } | ||||
1176 | | ||||
1177 | void XdgToplevelClient::handleMaximizeRequested() | ||||
1178 | { | ||||
1179 | maximize(MaximizeFull); | ||||
1180 | scheduleConfigure(); | ||||
1181 | } | ||||
1182 | | ||||
1183 | void XdgToplevelClient::handleUnmaximizeRequested() | ||||
1184 | { | ||||
1185 | maximize(MaximizeRestore); | ||||
1186 | scheduleConfigure(); | ||||
1187 | } | ||||
1188 | | ||||
1189 | void XdgToplevelClient::handleFullscreenRequested(OutputInterface *output) | ||||
1190 | { | ||||
1191 | Q_UNUSED(output) | ||||
1192 | setFullScreen(/* set */ true, /* user */ false); | ||||
1193 | scheduleConfigure(); | ||||
1194 | } | ||||
1195 | | ||||
1196 | void XdgToplevelClient::handleUnfullscreenRequested() | ||||
1197 | { | ||||
1198 | setFullScreen(/* set */ false, /* user */ false); | ||||
1199 | scheduleConfigure(); | ||||
1200 | } | ||||
1201 | | ||||
1202 | void XdgToplevelClient::handleMinimizeRequested() | ||||
1223 | { | 1203 | { | ||
1224 | performMouseCommand(Options::MouseMinimize, Cursors::self()->mouse()->pos()); | 1204 | performMouseCommand(Options::MouseMinimize, Cursors::self()->mouse()->pos()); | ||
1225 | } | 1205 | } | ||
1226 | 1206 | | |||
1227 | void XdgShellClient::handleMaximizeRequested(bool maximized) | 1207 | void XdgToplevelClient::handleTransientForChanged() | ||
1228 | { | 1208 | { | ||
1229 | // If the maximized state of the client hasn't been changed due to a window | 1209 | SurfaceInterface *transientForSurface = nullptr; | ||
1230 | // rule or because the requested state is the same as the current, then the | 1210 | if (XdgToplevelInterface *parentToplevel = m_shellSurface->parentXdgToplevel()) { | ||
1231 | // compositor still has to send a configure event. | 1211 | transientForSurface = parentToplevel->surface(); | ||
1232 | RequestGeometryBlocker blocker(this); | 1212 | } | ||
1213 | if (!transientForSurface) { | ||||
1214 | transientForSurface = waylandServer()->findForeignTransientForSurface(surface()); | ||||
1215 | } | ||||
1216 | AbstractClient *transientForClient = waylandServer()->findClient(transientForSurface); | ||||
1217 | if (transientForClient != transientFor()) { | ||||
1218 | if (transientFor()) { | ||||
1219 | transientFor()->removeTransient(this); | ||||
1220 | } | ||||
1221 | if (transientForClient) { | ||||
1222 | transientForClient->addTransient(this); | ||||
1223 | } | ||||
1224 | setTransientFor(transientForClient); | ||||
1225 | } | ||||
1226 | m_isTransient = transientForClient; | ||||
1227 | } | ||||
1228 | | ||||
1229 | void XdgToplevelClient::handleForeignTransientForChanged(SurfaceInterface *child) | ||||
1230 | { | ||||
1231 | if (surface() == child) { | ||||
1232 | handleTransientForChanged(); | ||||
1233 | } | ||||
1234 | } | ||||
1235 | | ||||
1236 | void XdgToplevelClient::handlePingTimeout(quint32 serial) | ||||
1237 | { | ||||
1238 | auto pingIt = m_pings.find(serial); | ||||
1239 | if (pingIt == m_pings.end()) { | ||||
1240 | return; | ||||
1241 | } | ||||
1242 | if (pingIt.value() == PingReason::CloseWindow) { | ||||
1243 | qCDebug(KWIN_CORE) << "Final ping timeout on a close attempt, asking to kill:" << caption(); | ||||
1244 | | ||||
1245 | // For internal windows, killing the window will delete this. | ||||
1246 | QPointer<QObject> guard(this); | ||||
1247 | killWindow(); | ||||
1248 | if (!guard) { | ||||
1249 | return; | ||||
1250 | } | ||||
1251 | } | ||||
1252 | m_pings.erase(pingIt); | ||||
1253 | } | ||||
1254 | | ||||
1255 | void XdgToplevelClient::handlePingDelayed(quint32 serial) | ||||
1256 | { | ||||
1257 | auto it = m_pings.find(serial); | ||||
1258 | if (it != m_pings.end()) { | ||||
1259 | qCDebug(KWIN_CORE) << "First ping timeout:" << caption(); | ||||
1260 | setUnresponsive(true); | ||||
1261 | } | ||||
1262 | } | ||||
1263 | | ||||
1264 | void XdgToplevelClient::handlePongReceived(quint32 serial) | ||||
1265 | { | ||||
1266 | auto it = m_pings.find(serial); | ||||
1267 | if (it != m_pings.end()) { | ||||
1268 | setUnresponsive(false); | ||||
1269 | m_pings.erase(it); | ||||
1270 | } | ||||
1271 | } | ||||
1272 | | ||||
1273 | void XdgToplevelClient::sendPing(PingReason reason) | ||||
1274 | { | ||||
1275 | XdgShellInterface *shell = m_shellSurface->shell(); | ||||
1276 | XdgSurfaceInterface *surface = m_shellSurface->xdgSurface(); | ||||
1277 | | ||||
1278 | const quint32 serial = shell->ping(surface); | ||||
1279 | m_pings.insert(serial, reason); | ||||
1280 | } | ||||
1281 | | ||||
1282 | void XdgToplevelClient::initialize() | ||||
1283 | { | ||||
1284 | blockGeometryUpdates(true); | ||||
1285 | | ||||
1286 | bool needsPlacement = !isInitialPositionSet(); | ||||
1287 | | ||||
1288 | if (supportsWindowRules()) { | ||||
1289 | setupWindowRules(false); | ||||
1290 | | ||||
1291 | const QRect originalGeometry = frameGeometry(); | ||||
1292 | const QRect ruledGeometry = rules()->checkGeometry(originalGeometry, true); | ||||
1293 | if (originalGeometry != ruledGeometry) { | ||||
1294 | setFrameGeometry(ruledGeometry); | ||||
1295 | } | ||||
1296 | maximize(rules()->checkMaximize(maximizeMode(), true)); | ||||
1297 | setDesktop(rules()->checkDesktop(desktop(), true)); | ||||
1298 | setDesktopFileName(rules()->checkDesktopFile(desktopFileName(), true).toUtf8()); | ||||
1299 | if (rules()->checkMinimize(isMinimized(), true)) { | ||||
1300 | minimize(true); // No animation. | ||||
1301 | } | ||||
1302 | setSkipTaskbar(rules()->checkSkipTaskbar(skipTaskbar(), true)); | ||||
1303 | setSkipPager(rules()->checkSkipPager(skipPager(), true)); | ||||
1304 | setSkipSwitcher(rules()->checkSkipSwitcher(skipSwitcher(), true)); | ||||
1305 | setKeepAbove(rules()->checkKeepAbove(keepAbove(), true)); | ||||
1306 | setKeepBelow(rules()->checkKeepBelow(keepBelow(), true)); | ||||
1307 | setShortcut(rules()->checkShortcut(shortcut().toString(), true)); | ||||
1308 | | ||||
1309 | // Don't place the client if its position is set by a rule. | ||||
1310 | if (rules()->checkPosition(invalidPoint, true) != invalidPoint) { | ||||
1311 | needsPlacement = false; | ||||
1312 | } | ||||
1313 | | ||||
1314 | // Don't place the client if the maximize state is set by a rule. | ||||
1315 | if (requestedMaximizeMode() != MaximizeRestore) { | ||||
1316 | needsPlacement = false; | ||||
1317 | } | ||||
1318 | | ||||
1319 | discardTemporaryRules(); | ||||
1320 | RuleBook::self()->discardUsed(this, false); // Remove Apply Now rules. | ||||
1321 | updateWindowRules(Rules::All); | ||||
1322 | } | ||||
1323 | if (isFullScreen()) { | ||||
1324 | needsPlacement = false; | ||||
1325 | } | ||||
1326 | if (needsPlacement) { | ||||
1327 | const QRect area = workspace()->clientArea(PlacementArea, Screens::self()->current(), desktop()); | ||||
1328 | placeIn(area); | ||||
1329 | } | ||||
1330 | | ||||
1331 | blockGeometryUpdates(false); | ||||
1332 | scheduleConfigure(); | ||||
1333 | updateColorScheme(); | ||||
1233 | 1334 | | |||
1234 | maximize(maximized ? MaximizeFull : MaximizeRestore); | 1335 | m_isInitialized = true; | ||
1235 | } | 1336 | } | ||
1236 | 1337 | | |||
1237 | void XdgShellClient::handleFullScreenRequested(bool fullScreen, OutputInterface *output) | 1338 | void XdgToplevelClient::updateMaximizeMode(MaximizeMode maximizeMode) | ||
1238 | { | 1339 | { | ||
1239 | // FIXME: Consider output as well. | 1340 | if (m_maximizeMode == maximizeMode) { | ||
1240 | Q_UNUSED(output); | 1341 | return; | ||
1241 | setFullScreen(fullScreen, false); | | |||
1242 | } | 1342 | } | ||
1243 | 1343 | m_maximizeMode = maximizeMode; | |||
1244 | void XdgShellClient::handleWindowMenuRequested(SeatInterface *seat, quint32 serial, const QPoint &surfacePos) | 1344 | updateWindowRules(Rules::MaximizeVert | Rules::MaximizeHoriz); | ||
1245 | { | 1345 | emit clientMaximizedStateChanged(this, maximizeMode); | ||
1246 | // FIXME: Check the seat and serial. | 1346 | emit clientMaximizedStateChanged(this, maximizeMode & MaximizeHorizontal, maximizeMode & MaximizeVertical); | ||
1247 | Q_UNUSED(seat) | | |||
1248 | Q_UNUSED(serial) | | |||
1249 | performMouseCommand(Options::MouseOperationsMenu, pos() + surfacePos); | | |||
1250 | } | 1347 | } | ||
1251 | 1348 | | |||
1252 | void XdgShellClient::handleGrabRequested(SeatInterface *seat, quint32 serial) | 1349 | void XdgToplevelClient::updateFullScreenMode(bool set) | ||
1253 | { | 1350 | { | ||
1254 | // FIXME: Check the seat and serial as well whether the parent had focus. | 1351 | if (m_isFullScreen == set) { | ||
1255 | Q_UNUSED(seat) | 1352 | return; | ||
1256 | Q_UNUSED(serial) | 1353 | } | ||
1257 | m_hasPopupGrab = true; | 1354 | m_isFullScreen = set; | ||
1355 | updateWindowRules(Rules::Fullscreen); | ||||
1356 | emit fullScreenChanged(); | ||||
1258 | } | 1357 | } | ||
1259 | 1358 | | |||
1260 | void XdgShellClient::handlePingDelayed(quint32 serial) | 1359 | void XdgToplevelClient::updateColorScheme() | ||
1261 | { | 1360 | { | ||
1262 | auto it = m_pingSerials.find(serial); | 1361 | if (m_paletteInterface) { | ||
1263 | if (it != m_pingSerials.end()) { | 1362 | AbstractClient::updateColorScheme(rules()->checkDecoColor(m_paletteInterface->palette())); | ||
1264 | qCDebug(KWIN_CORE) << "First ping timeout:" << caption(); | 1363 | } else { | ||
1265 | setUnresponsive(true); | 1364 | AbstractClient::updateColorScheme(rules()->checkDecoColor(QString())); | ||
1266 | } | 1365 | } | ||
1267 | } | 1366 | } | ||
1268 | 1367 | | |||
1269 | void XdgShellClient::handlePingTimeout(quint32 serial) | 1368 | void XdgToplevelClient::installAppMenu(AppMenuInterface *appMenu) | ||
1270 | { | 1369 | { | ||
1271 | auto it = m_pingSerials.find(serial); | 1370 | m_appMenuInterface = appMenu; | ||
1272 | if (it != m_pingSerials.end()) { | | |||
1273 | if (it.value() == PingReason::CloseWindow) { | | |||
1274 | qCDebug(KWIN_CORE) << "Final ping timeout on a close attempt, asking to kill:" << caption(); | | |||
1275 | 1371 | | |||
1276 | //for internal windows, killing the window will delete this | 1372 | auto updateMenu = [this](const AppMenuInterface::InterfaceAddress &address) { | ||
1277 | QPointer<QObject> guard(this); | 1373 | updateApplicationMenuServiceName(address.serviceName); | ||
1278 | killWindow(); | 1374 | updateApplicationMenuObjectPath(address.objectPath); | ||
1279 | if (!guard) { | 1375 | }; | ||
1280 | return; | 1376 | connect(m_appMenuInterface, &AppMenuInterface::addressChanged, this, updateMenu); | ||
1377 | updateMenu(appMenu->address()); | ||||
1281 | } | 1378 | } | ||
1379 | | ||||
1380 | void XdgToplevelClient::installServerDecoration(ServerSideDecorationInterface *decoration) | ||||
1381 | { | ||||
1382 | m_serverDecoration = decoration; | ||||
1383 | | ||||
1384 | connect(m_serverDecoration, &ServerSideDecorationInterface::destroyed, this, [this] { | ||||
1385 | if (!isClosing() && !isUnmapped()) { | ||||
1386 | updateDecoration(/* check_workspace_pos */ true); | ||||
1282 | } | 1387 | } | ||
1283 | m_pingSerials.erase(it); | 1388 | }); | ||
1389 | connect(m_serverDecoration, &ServerSideDecorationInterface::modeRequested, this, | ||||
1390 | [this] (ServerSideDecorationManagerInterface::Mode mode) { | ||||
1391 | const bool changed = mode != m_serverDecoration->mode(); | ||||
1392 | if (changed && !isUnmapped()) { | ||||
1393 | updateDecoration(/* check_workspace_pos */ false); | ||||
1284 | } | 1394 | } | ||
1285 | } | 1395 | } | ||
1286 | 1396 | ); | |||
1287 | void XdgShellClient::handlePongReceived(quint32 serial) | 1397 | if (!isUnmapped()) { | ||
1288 | { | 1398 | updateDecoration(/* check_workspace_pos */ true); | ||
1289 | auto it = m_pingSerials.find(serial); | | |||
1290 | if (it != m_pingSerials.end()) { | | |||
1291 | setUnresponsive(false); | | |||
1292 | m_pingSerials.erase(it); | | |||
1293 | } | 1399 | } | ||
1294 | } | 1400 | } | ||
1295 | 1401 | | |||
1296 | void XdgShellClient::handleCommitted() | 1402 | void XdgToplevelClient::installXdgDecoration(XdgToplevelDecorationV1Interface *decoration) | ||
1297 | { | 1403 | { | ||
1298 | if (!surface()->buffer()) { | 1404 | m_xdgDecoration = decoration; | ||
1299 | return; | | |||
1300 | } | | |||
1301 | 1405 | | |||
1302 | if (!m_hasWindowGeometry) { | 1406 | connect(m_xdgDecoration, &XdgToplevelDecorationV1Interface::destroyed, this, [this] { | ||
1303 | m_windowGeometry = surface()->boundingRect(); | 1407 | if (!isClosing()) { | ||
1408 | updateDecoration(/* check_workspace_pos */ true); | ||||
1304 | } | 1409 | } | ||
1305 | 1410 | }); | |||
1306 | updatePendingGeometry(); | 1411 | connect(m_xdgDecoration, &XdgToplevelDecorationV1Interface::preferredModeChanged, this, [this] { | ||
1307 | 1412 | // force is true as we must send a new configure response. | |||
1308 | setDepth((surface()->buffer()->hasAlphaChannel() && !isDesktop()) ? 32 : 24); | 1413 | updateDecoration(/* check_workspace_pos */ false, /* force */ true); | ||
1309 | markAsMapped(); | 1414 | }); | ||
1310 | } | 1415 | } | ||
1311 | 1416 | | |||
1312 | void XdgShellClient::resizeWithChecks(const QSize &size, ForceGeometry_t force) | 1417 | void XdgToplevelClient::installPalette(ServerSideDecorationPaletteInterface *palette) | ||
1313 | { | 1418 | { | ||
1314 | // don't allow growing larger than workarea | 1419 | m_paletteInterface = palette; | ||
1315 | const QRect area = workspace()->clientArea(WorkArea, this); | | |||
1316 | setFrameGeometry(QRect{pos(), size.boundedTo(area.size())}, force); | | |||
1317 | } | | |||
1318 | 1420 | | |||
1319 | void XdgShellClient::unmap() | 1421 | auto updatePalette = [this](const QString &palette) { | ||
1320 | { | 1422 | AbstractClient::updateColorScheme(rules()->checkDecoColor(palette)); | ||
1321 | m_unmapped = true; | 1423 | }; | ||
1322 | if (isMoveResize()) { | 1424 | connect(m_paletteInterface, &ServerSideDecorationPaletteInterface::paletteChanged, this, [=](const QString &palette) { | ||
1323 | leaveMoveResize(); | 1425 | updatePalette(palette); | ||
1324 | } | 1426 | }); | ||
1325 | m_requestedClientSize = QSize(0, 0); | 1427 | connect(m_paletteInterface, &QObject::destroyed, this, [=]() { | ||
1326 | destroyWindowManagementInterface(); | 1428 | updatePalette(QString()); | ||
1327 | if (Workspace::self()) { | 1429 | }); | ||
1328 | addWorkspaceRepaint(visibleRect()); | 1430 | updatePalette(palette->palette()); | ||
1329 | workspace()->clientHidden(this); | | |||
1330 | } | | |||
1331 | emit windowHidden(this); | | |||
1332 | } | 1431 | } | ||
1333 | 1432 | | |||
1334 | void XdgShellClient::installPlasmaShellSurface(PlasmaShellSurfaceInterface *surface) | 1433 | /** | ||
1434 | * \todo This whole plasma shell surface thing doesn't seem right. It turns xdg-toplevel into | ||||
1435 | * something completely different! Perhaps plasmashell surfaces need to be implemented via a | ||||
1436 | * proprietary protocol that doesn't piggyback on existing shell surface protocols. It'll lead | ||||
1437 | * to cleaner code and will be technically correct, but I'm not sure whether this is do-able. | ||||
1438 | */ | ||||
1439 | void XdgToplevelClient::installPlasmaShellSurface(PlasmaShellSurfaceInterface *shellSurface) | ||||
1335 | { | 1440 | { | ||
1336 | m_plasmaShellSurface = surface; | 1441 | m_plasmaShellSurface = shellSurface; | ||
1337 | auto updatePosition = [this, surface] { | 1442 | | ||
1338 | // That's a mis-use of doSetGeometry method. One should instead use move method. | 1443 | auto updatePosition = [this, shellSurface] { move(shellSurface->position()); }; | ||
1339 | QRect rect = QRect(surface->position(), size()); | 1444 | auto updateRole = [this, shellSurface] { | ||
1340 | doSetGeometry(rect); | | |||
1341 | }; | | |||
1342 | auto updateRole = [this, surface] { | | |||
1343 | NET::WindowType type = NET::Unknown; | 1445 | NET::WindowType type = NET::Unknown; | ||
1344 | switch (surface->role()) { | 1446 | switch (shellSurface->role()) { | ||
1345 | case PlasmaShellSurfaceInterface::Role::Desktop: | 1447 | case PlasmaShellSurfaceInterface::Role::Desktop: | ||
1346 | type = NET::Desktop; | 1448 | type = NET::Desktop; | ||
1347 | break; | 1449 | break; | ||
1348 | case PlasmaShellSurfaceInterface::Role::Panel: | 1450 | case PlasmaShellSurfaceInterface::Role::Panel: | ||
1349 | type = NET::Dock; | 1451 | type = NET::Dock; | ||
1350 | break; | 1452 | break; | ||
1351 | case PlasmaShellSurfaceInterface::Role::OnScreenDisplay: | 1453 | case PlasmaShellSurfaceInterface::Role::OnScreenDisplay: | ||
1352 | type = NET::OnScreenDisplay; | 1454 | type = NET::OnScreenDisplay; | ||
1353 | break; | 1455 | break; | ||
1354 | case PlasmaShellSurfaceInterface::Role::Notification: | 1456 | case PlasmaShellSurfaceInterface::Role::Notification: | ||
1355 | type = NET::Notification; | 1457 | type = NET::Notification; | ||
1356 | break; | 1458 | break; | ||
1357 | case PlasmaShellSurfaceInterface::Role::ToolTip: | 1459 | case PlasmaShellSurfaceInterface::Role::ToolTip: | ||
1358 | type = NET::Tooltip; | 1460 | type = NET::Tooltip; | ||
1359 | break; | 1461 | break; | ||
1360 | case PlasmaShellSurfaceInterface::Role::CriticalNotification: | 1462 | case PlasmaShellSurfaceInterface::Role::CriticalNotification: | ||
1361 | type = NET::CriticalNotification; | 1463 | type = NET::CriticalNotification; | ||
1362 | break; | 1464 | break; | ||
1363 | case PlasmaShellSurfaceInterface::Role::Normal: | 1465 | case PlasmaShellSurfaceInterface::Role::Normal: | ||
1364 | default: | 1466 | default: | ||
1365 | type = NET::Normal; | 1467 | type = NET::Normal; | ||
1366 | break; | 1468 | break; | ||
1367 | } | 1469 | } | ||
1368 | if (type != m_windowType) { | 1470 | if (m_windowType == type) { | ||
1471 | return; | ||||
1472 | } | ||||
1369 | m_windowType = type; | 1473 | m_windowType = type; | ||
1370 | if (m_windowType == NET::Desktop || type == NET::Dock || type == NET::OnScreenDisplay || type == NET::Notification || type == NET::Tooltip || type == NET::CriticalNotification) { | 1474 | switch (m_windowType) { | ||
1475 | case NET::Desktop: | ||||
1476 | case NET::Dock: | ||||
1477 | case NET::OnScreenDisplay: | ||||
1478 | case NET::Notification: | ||||
1479 | case NET::CriticalNotification: | ||||
1480 | case NET::Tooltip: | ||||
1371 | setOnAllDesktops(true); | 1481 | setOnAllDesktops(true); | ||
1482 | break; | ||||
1483 | default: | ||||
1484 | break; | ||||
1372 | } | 1485 | } | ||
1373 | workspace()->updateClientArea(); | 1486 | workspace()->updateClientArea(); | ||
1374 | } | | |||
1375 | }; | 1487 | }; | ||
1376 | connect(surface, &PlasmaShellSurfaceInterface::panelTakesFocusChanged , this, [this, surface]() { | 1488 | connect(shellSurface, &PlasmaShellSurfaceInterface::positionChanged, this, updatePosition); | ||
1377 | if (surface->panelTakesFocus()) { | 1489 | connect(shellSurface, &PlasmaShellSurfaceInterface::roleChanged, this, updateRole); | ||
1378 | workspace()->activateClient(this); | 1490 | connect(shellSurface, &PlasmaShellSurfaceInterface::panelBehaviorChanged, this, [this] { | ||
1379 | } | | |||
1380 | }); | | |||
1381 | connect(surface, &PlasmaShellSurfaceInterface::positionChanged, this, updatePosition); | | |||
1382 | connect(surface, &PlasmaShellSurfaceInterface::roleChanged, this, updateRole); | | |||
1383 | connect(surface, &PlasmaShellSurfaceInterface::panelBehaviorChanged, this, | | |||
1384 | [this] { | | |||
1385 | updateShowOnScreenEdge(); | 1491 | updateShowOnScreenEdge(); | ||
I can't see panelTakesFocusChanged. I strongly suspect this will break Kai's reply notifications. Arguably the event name doesn't match the usage, but this isn't the time and place for that. davidedmundson: I can't see panelTakesFocusChanged.
I strongly suspect this will break Kai's reply… | |||||
There's a good chance that I overlooked it while I was resolving merge conflicts. zzag: There's a good chance that I overlooked it while I was resolving merge conflicts. | |||||
1386 | workspace()->updateClientArea(); | 1492 | workspace()->updateClientArea(); | ||
1387 | } | 1493 | }); | ||
1388 | ); | 1494 | connect(shellSurface, &PlasmaShellSurfaceInterface::panelAutoHideHideRequested, this, [this] { | ||
1389 | connect(surface, &PlasmaShellSurfaceInterface::panelAutoHideHideRequested, this, | | |||
1390 | [this] { | | |||
1391 | hideClient(true); | 1495 | hideClient(true); | ||
1392 | m_plasmaShellSurface->hideAutoHidingPanel(); | 1496 | m_plasmaShellSurface->hideAutoHidingPanel(); | ||
1393 | updateShowOnScreenEdge(); | 1497 | updateShowOnScreenEdge(); | ||
1394 | } | 1498 | }); | ||
1395 | ); | 1499 | connect(shellSurface, &PlasmaShellSurfaceInterface::panelAutoHideShowRequested, this, [this] { | ||
1396 | connect(surface, &PlasmaShellSurfaceInterface::panelAutoHideShowRequested, this, | | |||
1397 | [this] { | | |||
1398 | hideClient(false); | 1500 | hideClient(false); | ||
1399 | ScreenEdges::self()->reserve(this, ElectricNone); | 1501 | ScreenEdges::self()->reserve(this, ElectricNone); | ||
1400 | m_plasmaShellSurface->showAutoHidingPanel(); | 1502 | m_plasmaShellSurface->showAutoHidingPanel(); | ||
1401 | } | 1503 | }); | ||
1402 | ); | 1504 | if (shellSurface->isPositionSet()) | ||
1403 | if (surface->isPositionSet()) | | |||
1404 | updatePosition(); | 1505 | updatePosition(); | ||
1405 | updateRole(); | 1506 | updateRole(); | ||
1406 | updateShowOnScreenEdge(); | 1507 | updateShowOnScreenEdge(); | ||
1407 | connect(this, &XdgShellClient::frameGeometryChanged, this, &XdgShellClient::updateShowOnScreenEdge); | 1508 | connect(this, &XdgToplevelClient::frameGeometryChanged, | ||
1509 | this, &XdgToplevelClient::updateShowOnScreenEdge); | ||||
1510 | connect(this, &XdgToplevelClient::windowShown, | ||||
1511 | this, &XdgToplevelClient::updateShowOnScreenEdge); | ||||
1408 | 1512 | | |||
1409 | setSkipTaskbar(surface->skipTaskbar()); | 1513 | setSkipTaskbar(shellSurface->skipTaskbar()); | ||
1410 | connect(surface, &PlasmaShellSurfaceInterface::skipTaskbarChanged, this, [this] { | 1514 | connect(shellSurface, &PlasmaShellSurfaceInterface::skipTaskbarChanged, this, [this] { | ||
1411 | setSkipTaskbar(m_plasmaShellSurface->skipTaskbar()); | 1515 | setSkipTaskbar(m_plasmaShellSurface->skipTaskbar()); | ||
1412 | }); | 1516 | }); | ||
1413 | 1517 | | |||
1414 | setSkipSwitcher(surface->skipSwitcher()); | 1518 | setSkipSwitcher(shellSurface->skipSwitcher()); | ||
1415 | connect(surface, &PlasmaShellSurfaceInterface::skipSwitcherChanged, this, [this] { | 1519 | connect(shellSurface, &PlasmaShellSurfaceInterface::skipSwitcherChanged, this, [this] { | ||
1416 | setSkipSwitcher(m_plasmaShellSurface->skipSwitcher()); | 1520 | setSkipSwitcher(m_plasmaShellSurface->skipSwitcher()); | ||
1417 | }); | 1521 | }); | ||
1418 | } | 1522 | } | ||
1419 | 1523 | | |||
1420 | void XdgShellClient::updateShowOnScreenEdge() | 1524 | void XdgToplevelClient::updateShowOnScreenEdge() | ||
1421 | { | 1525 | { | ||
1422 | if (!ScreenEdges::self()) { | 1526 | if (!ScreenEdges::self()) { | ||
1423 | return; | 1527 | return; | ||
1424 | } | 1528 | } | ||
1425 | if (m_unmapped || !m_plasmaShellSurface || m_plasmaShellSurface->role() != PlasmaShellSurfaceInterface::Role::Panel) { | 1529 | if (isUnmapped() || !m_plasmaShellSurface || | ||
1530 | m_plasmaShellSurface->role() != PlasmaShellSurfaceInterface::Role::Panel) { | ||||
1426 | ScreenEdges::self()->reserve(this, ElectricNone); | 1531 | ScreenEdges::self()->reserve(this, ElectricNone); | ||
1427 | return; | 1532 | return; | ||
1428 | } | 1533 | } | ||
1429 | if ((m_plasmaShellSurface->panelBehavior() == PlasmaShellSurfaceInterface::PanelBehavior::AutoHide && m_hidden) || | 1534 | const PlasmaShellSurfaceInterface::PanelBehavior panelBehavior = m_plasmaShellSurface->panelBehavior(); | ||
1430 | m_plasmaShellSurface->panelBehavior() == PlasmaShellSurfaceInterface::PanelBehavior::WindowsCanCover) { | 1535 | if ((panelBehavior == PlasmaShellSurfaceInterface::PanelBehavior::AutoHide && isHidden()) || | ||
1431 | // screen edge API requires an edge, thus we need to figure out which edge the window borders | 1536 | panelBehavior == PlasmaShellSurfaceInterface::PanelBehavior::WindowsCanCover) { | ||
1537 | // Screen edge API requires an edge, thus we need to figure out which edge the window borders. | ||||
1432 | const QRect clientGeometry = frameGeometry(); | 1538 | const QRect clientGeometry = frameGeometry(); | ||
1433 | Qt::Edges edges; | 1539 | Qt::Edges edges; | ||
1434 | for (int i = 0; i < screens()->count(); i++) { | 1540 | for (int i = 0; i < screens()->count(); i++) { | ||
1435 | const QRect screenGeometry = screens()->geometry(i); | 1541 | const QRect screenGeometry = screens()->geometry(i); | ||
1436 | if (screenGeometry.left() == clientGeometry.left()) { | 1542 | if (screenGeometry.left() == clientGeometry.left()) { | ||
1437 | edges |= Qt::LeftEdge; | 1543 | edges |= Qt::LeftEdge; | ||
1438 | } | 1544 | } | ||
1439 | if (screenGeometry.right() == clientGeometry.right()) { | 1545 | if (screenGeometry.right() == clientGeometry.right()) { | ||
1440 | edges |= Qt::RightEdge; | 1546 | edges |= Qt::RightEdge; | ||
1441 | } | 1547 | } | ||
1442 | if (screenGeometry.top() == clientGeometry.top()) { | 1548 | if (screenGeometry.top() == clientGeometry.top()) { | ||
1443 | edges |= Qt::TopEdge; | 1549 | edges |= Qt::TopEdge; | ||
1444 | } | 1550 | } | ||
1445 | if (screenGeometry.bottom() == clientGeometry.bottom()) { | 1551 | if (screenGeometry.bottom() == clientGeometry.bottom()) { | ||
1446 | edges |= Qt::BottomEdge; | 1552 | edges |= Qt::BottomEdge; | ||
1447 | } | 1553 | } | ||
1448 | } | 1554 | } | ||
1449 | // a panel might border multiple screen edges. E.g. a horizontal panel at the bottom will | 1555 | | ||
1450 | // also border the left and right edge | 1556 | // A panel might border multiple screen edges. E.g. a horizontal panel at the bottom will | ||
1451 | // let's remove such cases | 1557 | // also border the left and right edge. Let's remove such cases. | ||
1452 | if (edges.testFlag(Qt::LeftEdge) && edges.testFlag(Qt::RightEdge)) { | 1558 | if (edges & Qt::LeftEdge && edges & Qt::RightEdge) { | ||
1453 | edges = edges & (~(Qt::LeftEdge | Qt::RightEdge)); | 1559 | edges = edges & (~(Qt::LeftEdge | Qt::RightEdge)); | ||
1454 | } | 1560 | } | ||
1455 | if (edges.testFlag(Qt::TopEdge) && edges.testFlag(Qt::BottomEdge)) { | 1561 | if (edges & Qt::TopEdge && edges & Qt::BottomEdge) { | ||
1456 | edges = edges & (~(Qt::TopEdge | Qt::BottomEdge)); | 1562 | edges = edges & (~(Qt::TopEdge | Qt::BottomEdge)); | ||
1457 | } | 1563 | } | ||
1458 | // it's still possible that a panel borders two edges, e.g. bottom and left | 1564 | | ||
1459 | // in that case the one which is sharing more with the edge wins | 1565 | // It's still possible that a panel borders two edges, e.g. bottom and left | ||
1460 | auto check = [clientGeometry](Qt::Edges edges, Qt::Edge horiz, Qt::Edge vert) { | 1566 | // in that case the one which is sharing more with the edge wins. | ||
1461 | if (edges.testFlag(horiz) && edges.testFlag(vert)) { | 1567 | auto check = [clientGeometry](Qt::Edges edges, Qt::Edge horizontal, Qt::Edge vertical) { | ||
1568 | if (edges & horizontal && edges & vertical) { | ||||
1462 | if (clientGeometry.width() >= clientGeometry.height()) { | 1569 | if (clientGeometry.width() >= clientGeometry.height()) { | ||
1463 | return edges & ~horiz; | 1570 | return edges & ~horizontal; | ||
1464 | } else { | 1571 | } else { | ||
1465 | return edges & ~vert; | 1572 | return edges & ~vertical; | ||
1466 | } | 1573 | } | ||
1467 | } | 1574 | } | ||
1468 | return edges; | 1575 | return edges; | ||
1469 | }; | 1576 | }; | ||
1470 | edges = check(edges, Qt::LeftEdge, Qt::TopEdge); | 1577 | edges = check(edges, Qt::LeftEdge, Qt::TopEdge); | ||
1471 | edges = check(edges, Qt::LeftEdge, Qt::BottomEdge); | 1578 | edges = check(edges, Qt::LeftEdge, Qt::BottomEdge); | ||
1472 | edges = check(edges, Qt::RightEdge, Qt::TopEdge); | 1579 | edges = check(edges, Qt::RightEdge, Qt::TopEdge); | ||
1473 | edges = check(edges, Qt::RightEdge, Qt::BottomEdge); | 1580 | edges = check(edges, Qt::RightEdge, Qt::BottomEdge); | ||
1474 | 1581 | | |||
1475 | ElectricBorder border = ElectricNone; | 1582 | ElectricBorder border = ElectricNone; | ||
1476 | if (edges.testFlag(Qt::LeftEdge)) { | 1583 | if (edges & Qt::LeftEdge) { | ||
1477 | border = ElectricLeft; | 1584 | border = ElectricLeft; | ||
1478 | } | 1585 | } | ||
1479 | if (edges.testFlag(Qt::RightEdge)) { | 1586 | if (edges & Qt::RightEdge) { | ||
1480 | border = ElectricRight; | 1587 | border = ElectricRight; | ||
1481 | } | 1588 | } | ||
1482 | if (edges.testFlag(Qt::TopEdge)) { | 1589 | if (edges & Qt::TopEdge) { | ||
1483 | border = ElectricTop; | 1590 | border = ElectricTop; | ||
1484 | } | 1591 | } | ||
1485 | if (edges.testFlag(Qt::BottomEdge)) { | 1592 | if (edges & Qt::BottomEdge) { | ||
1486 | border = ElectricBottom; | 1593 | border = ElectricBottom; | ||
1487 | } | 1594 | } | ||
1488 | ScreenEdges::self()->reserve(this, border); | 1595 | ScreenEdges::self()->reserve(this, border); | ||
1489 | } else { | 1596 | } else { | ||
1490 | ScreenEdges::self()->reserve(this, ElectricNone); | 1597 | ScreenEdges::self()->reserve(this, ElectricNone); | ||
1491 | } | 1598 | } | ||
1492 | } | 1599 | } | ||
1493 | 1600 | | |||
1494 | bool XdgShellClient::isInitialPositionSet() const | 1601 | void XdgToplevelClient::setupWindowManagementIntegration() | ||
1495 | { | 1602 | { | ||
1496 | if (m_plasmaShellSurface) { | 1603 | if (isLockScreen()) { | ||
1497 | return m_plasmaShellSurface->isPositionSet(); | 1604 | return; | ||
1498 | } | 1605 | } | ||
1499 | return false; | 1606 | connect(this, &XdgToplevelClient::windowMapped, | ||
1607 | this, &XdgToplevelClient::setupWindowManagementInterface); | ||||
1608 | connect(this, &XdgToplevelClient::windowUnmapped, | ||||
1609 | this, &XdgToplevelClient::destroyWindowManagementInterface); | ||||
1500 | } | 1610 | } | ||
1501 | 1611 | | |||
1502 | void XdgShellClient::installAppMenu(AppMenuInterface *menu) | 1612 | void XdgToplevelClient::setupPlasmaShellIntegration() | ||
1503 | { | 1613 | { | ||
1504 | m_appMenuInterface = menu; | 1614 | connect(this, &XdgToplevelClient::windowMapped, | ||
1615 | this, &XdgToplevelClient::updateShowOnScreenEdge); | ||||
1616 | } | ||||
1505 | 1617 | | |||
1506 | auto updateMenu = [this](AppMenuInterface::InterfaceAddress address) { | 1618 | void XdgToplevelClient::setFullScreen(bool set, bool user) | ||
1507 | updateApplicationMenuServiceName(address.serviceName); | 1619 | { | ||
1508 | updateApplicationMenuObjectPath(address.objectPath); | 1620 | set = rules()->checkFullScreen(set); | ||
1509 | }; | 1621 | | ||
1510 | connect(m_appMenuInterface, &AppMenuInterface::addressChanged, this, [=](AppMenuInterface::InterfaceAddress address) { | 1622 | const bool wasFullscreen = isFullScreen(); | ||
1511 | updateMenu(address); | 1623 | if (wasFullscreen == set) { | ||
1512 | }); | 1624 | return; | ||
1513 | updateMenu(menu->address()); | 1625 | } | ||
1626 | if (isSpecialWindow()) { | ||||
1627 | return; | ||||
1628 | } | ||||
1629 | if (user && !userCanSetFullScreen()) { | ||||
1630 | return; | ||||
1631 | } | ||||
1632 | | ||||
1633 | if (wasFullscreen) { | ||||
1634 | workspace()->updateFocusMousePosition(Cursors::self()->mouse()->pos()); // may cause leave event | ||||
1635 | } else { | ||||
1636 | m_fullScreenGeometryRestore = frameGeometry(); | ||||
1637 | } | ||||
1638 | m_isFullScreen = set; | ||||
1639 | | ||||
1640 | if (set) { | ||||
1641 | workspace()->raiseClient(this); | ||||
1642 | } | ||||
1643 | StackingUpdatesBlocker blocker1(workspace()); | ||||
1644 | GeometryUpdatesBlocker blocker2(this); | ||||
1645 | | ||||
1646 | workspace()->updateClientLayer(this); // active fullscreens get different layer | ||||
1647 | updateDecoration(false, false); | ||||
1648 | | ||||
1649 | if (set) { | ||||
1650 | setFrameGeometry(workspace()->clientArea(FullScreenArea, this)); | ||||
1651 | } else { | ||||
1652 | if (m_fullScreenGeometryRestore.isValid()) { | ||||
1653 | int currentScreen = screen(); | ||||
1654 | setFrameGeometry(QRect(m_fullScreenGeometryRestore.topLeft(), | ||||
1655 | constrainFrameSize(m_fullScreenGeometryRestore.size()))); | ||||
1656 | if( currentScreen != screen()) | ||||
1657 | workspace()->sendClientToScreen( this, currentScreen ); | ||||
1658 | } else { | ||||
1659 | // this can happen when the window was first shown already fullscreen, | ||||
1660 | // so let the client set the size by itself | ||||
1661 | setFrameGeometry(QRect(workspace()->clientArea(PlacementArea, this).topLeft(), QSize(0, 0))); | ||||
1662 | } | ||||
1663 | } | ||||
1664 | | ||||
1665 | scheduleConfigure(); | ||||
1666 | | ||||
1667 | updateWindowRules(Rules::Fullscreen|Rules::Position|Rules::Size); | ||||
1668 | emit fullScreenChanged(); | ||||
1669 | } | ||||
1670 | | ||||
1671 | /** | ||||
1672 | * \todo Move to AbstractClient. | ||||
1673 | */ | ||||
1674 | static bool changeMaximizeRecursion = false; | ||||
1675 | void XdgToplevelClient::changeMaximize(bool horizontal, bool vertical, bool adjust) | ||||
1676 | { | ||||
1677 | if (changeMaximizeRecursion) { | ||||
1678 | return; | ||||
1679 | } | ||||
1680 | | ||||
1681 | if (!isResizable()) { | ||||
1682 | return; | ||||
1683 | } | ||||
1684 | | ||||
1685 | const QRect clientArea = isElectricBorderMaximizing() ? | ||||
1686 | workspace()->clientArea(MaximizeArea, Cursors::self()->mouse()->pos(), desktop()) : | ||||
1687 | workspace()->clientArea(MaximizeArea, this); | ||||
1688 | | ||||
1689 | const MaximizeMode oldMode = m_requestedMaximizeMode; | ||||
1690 | const QRect oldGeometry = frameGeometry(); | ||||
1691 | | ||||
1692 | // 'adjust == true' means to update the size only, e.g. after changing workspace size | ||||
1693 | if (!adjust) { | ||||
1694 | if (vertical) | ||||
1695 | m_requestedMaximizeMode = MaximizeMode(m_requestedMaximizeMode ^ MaximizeVertical); | ||||
1696 | if (horizontal) | ||||
1697 | m_requestedMaximizeMode = MaximizeMode(m_requestedMaximizeMode ^ MaximizeHorizontal); | ||||
1698 | } | ||||
1699 | | ||||
1700 | m_requestedMaximizeMode = rules()->checkMaximize(m_requestedMaximizeMode); | ||||
1701 | if (!adjust && m_requestedMaximizeMode == oldMode) { | ||||
1702 | return; | ||||
1703 | } | ||||
1704 | | ||||
1705 | StackingUpdatesBlocker blocker(workspace()); | ||||
1706 | | ||||
1707 | // call into decoration update borders | ||||
1708 | if (isDecorated() && decoration()->client() && !(options->borderlessMaximizedWindows() && m_requestedMaximizeMode == KWin::MaximizeFull)) { | ||||
1709 | changeMaximizeRecursion = true; | ||||
1710 | const auto c = decoration()->client().toStrongRef(); | ||||
1711 | if ((m_requestedMaximizeMode & MaximizeVertical) != (oldMode & MaximizeVertical)) { | ||||
1712 | emit c->maximizedVerticallyChanged(m_requestedMaximizeMode & MaximizeVertical); | ||||
1713 | } | ||||
1714 | if ((m_requestedMaximizeMode & MaximizeHorizontal) != (oldMode & MaximizeHorizontal)) { | ||||
1715 | emit c->maximizedHorizontallyChanged(m_requestedMaximizeMode & MaximizeHorizontal); | ||||
1716 | } | ||||
1717 | if ((m_requestedMaximizeMode == MaximizeFull) != (oldMode == MaximizeFull)) { | ||||
1718 | emit c->maximizedChanged(m_requestedMaximizeMode & MaximizeFull); | ||||
1719 | } | ||||
1720 | changeMaximizeRecursion = false; | ||||
1721 | } | ||||
1722 | | ||||
1723 | if (options->borderlessMaximizedWindows()) { | ||||
1724 | // triggers a maximize change. | ||||
1725 | // The next setNoBorder interation will exit since there's no change but the first recursion pullutes the restore geometry | ||||
1726 | changeMaximizeRecursion = true; | ||||
1727 | setNoBorder(rules()->checkNoBorder(m_requestedMaximizeMode == MaximizeFull)); | ||||
1728 | changeMaximizeRecursion = false; | ||||
1729 | } | ||||
1730 | | ||||
1731 | // Conditional quick tiling exit points | ||||
1732 | const auto oldQuickTileMode = quickTileMode(); | ||||
1733 | if (quickTileMode() != QuickTileMode(QuickTileFlag::None)) { | ||||
1734 | if (oldMode == MaximizeFull && | ||||
1735 | !clientArea.contains(geometryRestore().center())) { | ||||
1736 | // Not restoring on the same screen | ||||
1737 | // TODO: The following doesn't work for some reason | ||||
1738 | //quick_tile_mode = QuickTileNone; // And exit quick tile mode manually | ||||
1739 | } else if ((oldMode == MaximizeVertical && m_requestedMaximizeMode == MaximizeRestore) || | ||||
1740 | (oldMode == MaximizeFull && m_requestedMaximizeMode == MaximizeHorizontal)) { | ||||
1741 | // Modifying geometry of a tiled window | ||||
1742 | updateQuickTileMode(QuickTileFlag::None); // Exit quick tile mode without restoring geometry | ||||
1743 | } | ||||
1744 | } | ||||
1745 | | ||||
1746 | if (m_requestedMaximizeMode == MaximizeFull) { | ||||
1747 | setGeometryRestore(oldGeometry); | ||||
1748 | // TODO: Client has more checks | ||||
1749 | if (options->electricBorderMaximize()) { | ||||
1750 | updateQuickTileMode(QuickTileFlag::Maximize); | ||||
1751 | } else { | ||||
1752 | updateQuickTileMode(QuickTileFlag::None); | ||||
1753 | } | ||||
1754 | if (quickTileMode() != oldQuickTileMode) { | ||||
1755 | emit quickTileModeChanged(); | ||||
1756 | } | ||||
1757 | setFrameGeometry(workspace()->clientArea(MaximizeArea, this)); | ||||
1758 | workspace()->raiseClient(this); | ||||
1759 | } else { | ||||
1760 | if (m_requestedMaximizeMode == MaximizeRestore) { | ||||
1761 | updateQuickTileMode(QuickTileFlag::None); | ||||
1762 | } | ||||
1763 | if (quickTileMode() != oldQuickTileMode) { | ||||
1764 | emit quickTileModeChanged(); | ||||
1765 | } | ||||
1766 | | ||||
1767 | if (geometryRestore().isValid()) { | ||||
1768 | setFrameGeometry(geometryRestore()); | ||||
1769 | } else { | ||||
1770 | setFrameGeometry(workspace()->clientArea(PlacementArea, this)); | ||||
1771 | } | ||||
1772 | } | ||||
1773 | | ||||
1774 | scheduleConfigure(); | ||||
1775 | } | ||||
1776 | | ||||
1777 | XdgPopupClient::XdgPopupClient(XdgPopupInterface *shellSurface) | ||||
1778 | : XdgSurfaceClient(shellSurface->xdgSurface()) | ||||
1779 | , m_shellSurface(shellSurface) | ||||
1780 | { | ||||
1781 | setDesktop(VirtualDesktopManager::self()->current()); | ||||
1782 | | ||||
1783 | connect(shellSurface, &XdgPopupInterface::grabRequested, | ||||
1784 | this, &XdgPopupClient::handleGrabRequested); | ||||
1785 | connect(shellSurface, &XdgPopupInterface::initializeRequested, | ||||
1786 | this, &XdgPopupClient::initialize); | ||||
1787 | connect(shellSurface, &XdgPopupInterface::destroyed, | ||||
1788 | this, &XdgPopupClient::destroyClient); | ||||
1789 | | ||||
1790 | // The xdg-shell spec states that the parent xdg-surface may be null if it is specified | ||||
1791 | // via "some other protocol," but we don't support any such protocol yet. Notice that the | ||||
1792 | // xdg-foreign protocol is only for toplevel surfaces. | ||||
1793 | | ||||
1794 | XdgSurfaceInterface *parentShellSurface = shellSurface->parentXdgSurface(); | ||||
1795 | AbstractClient *parentClient = waylandServer()->findClient(parentShellSurface->surface()); | ||||
1796 | parentClient->addTransient(this); | ||||
1797 | setTransientFor(parentClient); | ||||
1798 | } | ||||
1799 | | ||||
1800 | XdgPopupClient::~XdgPopupClient() | ||||
1801 | { | ||||
1514 | } | 1802 | } | ||
1515 | 1803 | | |||
1516 | void XdgShellClient::installPalette(ServerSideDecorationPaletteInterface *palette) | 1804 | void XdgPopupClient::debug(QDebug &stream) const | ||
1517 | { | 1805 | { | ||
1518 | m_paletteInterface = palette; | 1806 | stream << this; | ||
1519 | | ||||
1520 | auto updatePalette = [this](const QString &palette) { | | |||
1521 | AbstractClient::updateColorScheme(rules()->checkDecoColor(palette)); | | |||
1522 | }; | | |||
1523 | connect(m_paletteInterface, &ServerSideDecorationPaletteInterface::paletteChanged, this, [=](const QString &palette) { | | |||
1524 | updatePalette(palette); | | |||
1525 | }); | | |||
1526 | connect(m_paletteInterface, &QObject::destroyed, this, [=]() { | | |||
1527 | updatePalette(QString()); | | |||
1528 | }); | | |||
1529 | updatePalette(palette->palette()); | | |||
1530 | } | 1807 | } | ||
1531 | 1808 | | |||
1532 | void XdgShellClient::updateColorScheme() | 1809 | NET::WindowType XdgPopupClient::windowType(bool direct, int supported_types) const | ||
1533 | { | 1810 | { | ||
1534 | if (m_paletteInterface) { | 1811 | Q_UNUSED(direct) | ||
1535 | AbstractClient::updateColorScheme(rules()->checkDecoColor(m_paletteInterface->palette())); | 1812 | Q_UNUSED(supported_types) | ||
1536 | } else { | 1813 | return NET::Normal; | ||
1537 | AbstractClient::updateColorScheme(rules()->checkDecoColor(QString())); | | |||
1538 | } | 1814 | } | ||
1815 | | ||||
1816 | bool XdgPopupClient::hasPopupGrab() const | ||||
1817 | { | ||||
1818 | return m_haveExplicitGrab; | ||||
1539 | } | 1819 | } | ||
1540 | 1820 | | |||
1541 | void XdgShellClient::updateMaximizeMode(MaximizeMode maximizeMode) | 1821 | void XdgPopupClient::popupDone() | ||
1542 | { | 1822 | { | ||
1543 | if (maximizeMode == m_maximizeMode) { | 1823 | m_shellSurface->sendPopupDone(); | ||
1544 | return; | | |||
1545 | } | 1824 | } | ||
1546 | 1825 | | |||
1547 | m_maximizeMode = maximizeMode; | 1826 | bool XdgPopupClient::isPopupWindow() const | ||
1548 | updateWindowRules(Rules::MaximizeHoriz | Rules::MaximizeVert | Rules::Position | Rules::Size); | 1827 | { | ||
1828 | return true; | ||||
1829 | } | ||||
1549 | 1830 | | |||
1550 | emit clientMaximizedStateChanged(this, m_maximizeMode); | 1831 | bool XdgPopupClient::isTransient() const | ||
1551 | emit clientMaximizedStateChanged(this, m_maximizeMode & MaximizeHorizontal, m_maximizeMode & MaximizeVertical); | 1832 | { | ||
1833 | return true; | ||||
1552 | } | 1834 | } | ||
1553 | 1835 | | |||
1554 | bool XdgShellClient::hasStrut() const | 1836 | bool XdgPopupClient::isResizable() const | ||
1555 | { | 1837 | { | ||
1556 | if (!isShown(true)) { | | |||
1557 | return false; | 1838 | return false; | ||
1558 | } | 1839 | } | ||
1559 | if (!m_plasmaShellSurface) { | 1840 | | ||
1841 | bool XdgPopupClient::isMovable() const | ||||
1842 | { | ||||
1560 | return false; | 1843 | return false; | ||
1561 | } | 1844 | } | ||
1562 | if (m_plasmaShellSurface->role() != PlasmaShellSurfaceInterface::Role::Panel) { | 1845 | | ||
1846 | bool XdgPopupClient::isMovableAcrossScreens() const | ||||
1847 | { | ||||
1563 | return false; | 1848 | return false; | ||
1564 | } | 1849 | } | ||
1565 | return m_plasmaShellSurface->panelBehavior() == PlasmaShellSurfaceInterface::PanelBehavior::AlwaysVisible; | | |||
1566 | } | | |||
1567 | 1850 | | |||
1568 | quint32 XdgShellClient::windowId() const | 1851 | bool XdgPopupClient::hasTransientPlacementHint() const | ||
1569 | { | 1852 | { | ||
1570 | return m_windowId; | 1853 | return true; | ||
1571 | } | 1854 | } | ||
1572 | 1855 | | |||
1573 | void XdgShellClient::updateIcon() | 1856 | static QPoint popupOffset(const QRect &anchorRect, const Qt::Edges anchorEdge, | ||
1857 | const Qt::Edges gravity, const QSize popupSize) | ||||
1574 | { | 1858 | { | ||
1575 | const QString waylandIconName = QStringLiteral("wayland"); | 1859 | QPoint anchorPoint; | ||
1576 | const QString dfIconName = iconFromDesktopFile(); | 1860 | switch (anchorEdge & (Qt::LeftEdge | Qt::RightEdge)) { | ||
1577 | const QString iconName = dfIconName.isEmpty() ? waylandIconName : dfIconName; | 1861 | case Qt::LeftEdge: | ||
1578 | if (iconName == icon().name()) { | 1862 | anchorPoint.setX(anchorRect.x()); | ||
1579 | return; | 1863 | break; | ||
1864 | case Qt::RightEdge: | ||||
1865 | anchorPoint.setX(anchorRect.x() + anchorRect.width()); | ||||
1866 | break; | ||||
1867 | default: | ||||
1868 | anchorPoint.setX(qRound(anchorRect.x() + anchorRect.width() / 2.0)); | ||||
1580 | } | 1869 | } | ||
1581 | setIcon(QIcon::fromTheme(iconName)); | 1870 | switch (anchorEdge & (Qt::TopEdge | Qt::BottomEdge)) { | ||
1871 | case Qt::TopEdge: | ||||
1872 | anchorPoint.setY(anchorRect.y()); | ||||
1873 | break; | ||||
1874 | case Qt::BottomEdge: | ||||
1875 | anchorPoint.setY(anchorRect.y() + anchorRect.height()); | ||||
1876 | break; | ||||
1877 | default: | ||||
1878 | anchorPoint.setY(qRound(anchorRect.y() + anchorRect.height() / 2.0)); | ||||
1582 | } | 1879 | } | ||
1583 | 1880 | | |||
1584 | bool XdgShellClient::isTransient() const | 1881 | // calculate where the top left point of the popup will end up with the applied gravity | ||
1585 | { | 1882 | // gravity indicates direction. i.e if gravitating towards the top the popup's bottom edge | ||
1586 | return m_transient; | 1883 | // will next to the anchor point | ||
1884 | QPoint popupPosAdjust; | ||||
1885 | switch (gravity & (Qt::LeftEdge | Qt::RightEdge)) { | ||||
1886 | case Qt::LeftEdge: | ||||
1887 | popupPosAdjust.setX(-popupSize.width()); | ||||
1888 | break; | ||||
1889 | case Qt::RightEdge: | ||||
1890 | popupPosAdjust.setX(0); | ||||
1891 | break; | ||||
1892 | default: | ||||
1893 | popupPosAdjust.setX(qRound(-popupSize.width() / 2.0)); | ||||
1894 | } | ||||
1895 | switch (gravity & (Qt::TopEdge | Qt::BottomEdge)) { | ||||
1896 | case Qt::TopEdge: | ||||
1897 | popupPosAdjust.setY(-popupSize.height()); | ||||
1898 | break; | ||||
1899 | case Qt::BottomEdge: | ||||
1900 | popupPosAdjust.setY(0); | ||||
1901 | break; | ||||
1902 | default: | ||||
1903 | popupPosAdjust.setY(qRound(-popupSize.height() / 2.0)); | ||||
1587 | } | 1904 | } | ||
1588 | 1905 | | |||
1589 | bool XdgShellClient::hasTransientPlacementHint() const | 1906 | return anchorPoint + popupPosAdjust; | ||
1590 | { | | |||
1591 | return isTransient() && transientFor() && m_xdgShellPopup; | | |||
1592 | } | 1907 | } | ||
1593 | 1908 | | |||
1594 | QRect XdgShellClient::transientPlacement(const QRect &bounds) const | 1909 | QRect XdgPopupClient::transientPlacement(const QRect &bounds) const | ||
1595 | { | 1910 | { | ||
1596 | Q_ASSERT(m_xdgShellPopup); | 1911 | const XdgPositioner positioner = m_shellSurface->positioner(); | ||
1597 | 1912 | const QSize desiredSize = isUnmapped() ? positioner.size() : size(); | |||
1598 | QRect anchorRect; | | |||
1599 | Qt::Edges anchorEdge; | | |||
1600 | Qt::Edges gravity; | | |||
1601 | QPoint offset; | | |||
1602 | PositionerConstraints constraintAdjustments; | | |||
1603 | QSize size = frameGeometry().size(); | | |||
1604 | 1913 | | |||
1605 | const QPoint parentClientPos = transientFor()->pos() + transientFor()->clientPos(); | 1914 | const QPoint parentPosition = transientFor()->framePosToClientPos(transientFor()->pos()); | ||
1606 | 1915 | | |||
1607 | // returns if a target is within the supplied bounds, optional edges argument states which side to check | 1916 | // returns if a target is within the supplied bounds, optional edges argument states which side to check | ||
1608 | auto inBounds = [bounds](const QRect &target, Qt::Edges edges = Qt::LeftEdge | Qt::RightEdge | Qt::TopEdge | Qt::BottomEdge) -> bool { | 1917 | auto inBounds = [bounds](const QRect &target, Qt::Edges edges = Qt::LeftEdge | Qt::RightEdge | Qt::TopEdge | Qt::BottomEdge) -> bool { | ||
1609 | if (edges & Qt::LeftEdge && target.left() < bounds.left()) { | 1918 | if (edges & Qt::LeftEdge && target.left() < bounds.left()) { | ||
1610 | return false; | 1919 | return false; | ||
1611 | } | 1920 | } | ||
1612 | if (edges & Qt::TopEdge && target.top() < bounds.top()) { | 1921 | if (edges & Qt::TopEdge && target.top() < bounds.top()) { | ||
1613 | return false; | 1922 | return false; | ||
1614 | } | 1923 | } | ||
1615 | if (edges & Qt::RightEdge && target.right() > bounds.right()) { | 1924 | if (edges & Qt::RightEdge && target.right() > bounds.right()) { | ||
1616 | //normal QRect::right issue cancels out | 1925 | //normal QRect::right issue cancels out | ||
1617 | return false; | 1926 | return false; | ||
1618 | } | 1927 | } | ||
1619 | if (edges & Qt::BottomEdge && target.bottom() > bounds.bottom()) { | 1928 | if (edges & Qt::BottomEdge && target.bottom() > bounds.bottom()) { | ||
1620 | return false; | 1929 | return false; | ||
1621 | } | 1930 | } | ||
1622 | return true; | 1931 | return true; | ||
1623 | }; | 1932 | }; | ||
1624 | 1933 | | |||
1625 | anchorRect = m_xdgShellPopup->anchorRect(); | 1934 | QRect popupRect(popupOffset(positioner.anchorRect(), positioner.anchorEdges(), positioner.gravityEdges(), desiredSize) + positioner.offset() + parentPosition, desiredSize); | ||
1626 | anchorEdge = m_xdgShellPopup->anchorEdge(); | | |||
1627 | gravity = m_xdgShellPopup->gravity(); | | |||
1628 | offset = m_xdgShellPopup->anchorOffset(); | | |||
1629 | constraintAdjustments = m_xdgShellPopup->constraintAdjustments(); | | |||
1630 | if (!size.isValid()) { | | |||
1631 | size = m_xdgShellPopup->initialSize(); | | |||
1632 | } | | |||
1633 | | ||||
1634 | QRect popupRect(popupOffset(anchorRect, anchorEdge, gravity, size) + offset + parentClientPos, size); | | |||
1635 | 1935 | | |||
1636 | //if that fits, we don't need to do anything | 1936 | //if that fits, we don't need to do anything | ||
1637 | if (inBounds(popupRect)) { | 1937 | if (inBounds(popupRect)) { | ||
1638 | return popupRect; | 1938 | return popupRect; | ||
1639 | } | 1939 | } | ||
1640 | //otherwise apply constraint adjustment per axis in order XDG Shell Popup states | 1940 | //otherwise apply constraint adjustment per axis in order XDG Shell Popup states | ||
1641 | 1941 | | |||
1642 | if (constraintAdjustments & PositionerConstraint::FlipX) { | 1942 | if (positioner.flipConstraintAdjustments() & Qt::Horizontal) { | ||
1643 | if (!inBounds(popupRect, Qt::LeftEdge | Qt::RightEdge)) { | 1943 | if (!inBounds(popupRect, Qt::LeftEdge | Qt::RightEdge)) { | ||
1644 | //flip both edges (if either bit is set, XOR both) | 1944 | //flip both edges (if either bit is set, XOR both) | ||
1645 | auto flippedAnchorEdge = anchorEdge; | 1945 | auto flippedAnchorEdge = positioner.anchorEdges(); | ||
1646 | if (flippedAnchorEdge & (Qt::LeftEdge | Qt::RightEdge)) { | 1946 | if (flippedAnchorEdge & (Qt::LeftEdge | Qt::RightEdge)) { | ||
1647 | flippedAnchorEdge ^= (Qt::LeftEdge | Qt::RightEdge); | 1947 | flippedAnchorEdge ^= (Qt::LeftEdge | Qt::RightEdge); | ||
1648 | } | 1948 | } | ||
1649 | auto flippedGravity = gravity; | 1949 | auto flippedGravity = positioner.gravityEdges(); | ||
1650 | if (flippedGravity & (Qt::LeftEdge | Qt::RightEdge)) { | 1950 | if (flippedGravity & (Qt::LeftEdge | Qt::RightEdge)) { | ||
1651 | flippedGravity ^= (Qt::LeftEdge | Qt::RightEdge); | 1951 | flippedGravity ^= (Qt::LeftEdge | Qt::RightEdge); | ||
1652 | } | 1952 | } | ||
1653 | auto flippedPopupRect = QRect(popupOffset(anchorRect, flippedAnchorEdge, flippedGravity, size) + offset + parentClientPos, size); | 1953 | auto flippedPopupRect = QRect(popupOffset(positioner.anchorRect(), flippedAnchorEdge, flippedGravity, desiredSize) + positioner.offset() + parentPosition, desiredSize); | ||
1654 | 1954 | | |||
1655 | //if it still doesn't fit we should continue with the unflipped version | 1955 | //if it still doesn't fit we should continue with the unflipped version | ||
1656 | if (inBounds(flippedPopupRect, Qt::LeftEdge | Qt::RightEdge)) { | 1956 | if (inBounds(flippedPopupRect, Qt::LeftEdge | Qt::RightEdge)) { | ||
1657 | popupRect.moveLeft(flippedPopupRect.left()); | 1957 | popupRect.moveLeft(flippedPopupRect.left()); | ||
1658 | } | 1958 | } | ||
1659 | } | 1959 | } | ||
1660 | } | 1960 | } | ||
1661 | if (constraintAdjustments & PositionerConstraint::SlideX) { | 1961 | if (positioner.slideConstraintAdjustments() & Qt::Horizontal) { | ||
1662 | if (!inBounds(popupRect, Qt::LeftEdge)) { | 1962 | if (!inBounds(popupRect, Qt::LeftEdge)) { | ||
1663 | popupRect.moveLeft(bounds.left()); | 1963 | popupRect.moveLeft(bounds.left()); | ||
1664 | } | 1964 | } | ||
1665 | if (!inBounds(popupRect, Qt::RightEdge)) { | 1965 | if (!inBounds(popupRect, Qt::RightEdge)) { | ||
1666 | popupRect.moveRight(bounds.right()); | 1966 | popupRect.moveRight(bounds.right()); | ||
1667 | } | 1967 | } | ||
1668 | } | 1968 | } | ||
1669 | if (constraintAdjustments & PositionerConstraint::ResizeX) { | 1969 | if (positioner.resizeConstraintAdjustments() & Qt::Horizontal) { | ||
1670 | QRect unconstrainedRect = popupRect; | 1970 | QRect unconstrainedRect = popupRect; | ||
1671 | 1971 | | |||
1672 | if (!inBounds(unconstrainedRect, Qt::LeftEdge)) { | 1972 | if (!inBounds(unconstrainedRect, Qt::LeftEdge)) { | ||
1673 | unconstrainedRect.setLeft(bounds.left()); | 1973 | unconstrainedRect.setLeft(bounds.left()); | ||
1674 | } | 1974 | } | ||
1675 | if (!inBounds(unconstrainedRect, Qt::RightEdge)) { | 1975 | if (!inBounds(unconstrainedRect, Qt::RightEdge)) { | ||
1676 | unconstrainedRect.setRight(bounds.right()); | 1976 | unconstrainedRect.setRight(bounds.right()); | ||
1677 | } | 1977 | } | ||
1678 | 1978 | | |||
1679 | if (unconstrainedRect.isValid()) { | 1979 | if (unconstrainedRect.isValid()) { | ||
1680 | popupRect = unconstrainedRect; | 1980 | popupRect = unconstrainedRect; | ||
1681 | } | 1981 | } | ||
1682 | } | 1982 | } | ||
1683 | 1983 | | |||
1684 | if (constraintAdjustments & PositionerConstraint::FlipY) { | 1984 | if (positioner.flipConstraintAdjustments() & Qt::Vertical) { | ||
1685 | if (!inBounds(popupRect, Qt::TopEdge | Qt::BottomEdge)) { | 1985 | if (!inBounds(popupRect, Qt::TopEdge | Qt::BottomEdge)) { | ||
1686 | //flip both edges (if either bit is set, XOR both) | 1986 | //flip both edges (if either bit is set, XOR both) | ||
1687 | auto flippedAnchorEdge = anchorEdge; | 1987 | auto flippedAnchorEdge = positioner.anchorEdges(); | ||
1688 | if (flippedAnchorEdge & (Qt::TopEdge | Qt::BottomEdge)) { | 1988 | if (flippedAnchorEdge & (Qt::TopEdge | Qt::BottomEdge)) { | ||
1689 | flippedAnchorEdge ^= (Qt::TopEdge | Qt::BottomEdge); | 1989 | flippedAnchorEdge ^= (Qt::TopEdge | Qt::BottomEdge); | ||
1690 | } | 1990 | } | ||
1691 | auto flippedGravity = gravity; | 1991 | auto flippedGravity = positioner.gravityEdges(); | ||
1692 | if (flippedGravity & (Qt::TopEdge | Qt::BottomEdge)) { | 1992 | if (flippedGravity & (Qt::TopEdge | Qt::BottomEdge)) { | ||
1693 | flippedGravity ^= (Qt::TopEdge | Qt::BottomEdge); | 1993 | flippedGravity ^= (Qt::TopEdge | Qt::BottomEdge); | ||
1694 | } | 1994 | } | ||
1695 | auto flippedPopupRect = QRect(popupOffset(anchorRect, flippedAnchorEdge, flippedGravity, size) + offset + parentClientPos, size); | 1995 | auto flippedPopupRect = QRect(popupOffset(positioner.anchorRect(), flippedAnchorEdge, flippedGravity, desiredSize) + positioner.offset() + parentPosition, desiredSize); | ||
1696 | 1996 | | |||
1697 | //if it still doesn't fit we should continue with the unflipped version | 1997 | //if it still doesn't fit we should continue with the unflipped version | ||
1698 | if (inBounds(flippedPopupRect, Qt::TopEdge | Qt::BottomEdge)) { | 1998 | if (inBounds(flippedPopupRect, Qt::TopEdge | Qt::BottomEdge)) { | ||
1699 | popupRect.moveTop(flippedPopupRect.top()); | 1999 | popupRect.moveTop(flippedPopupRect.top()); | ||
1700 | } | 2000 | } | ||
1701 | } | 2001 | } | ||
1702 | } | 2002 | } | ||
1703 | if (constraintAdjustments & PositionerConstraint::SlideY) { | 2003 | if (positioner.slideConstraintAdjustments() & Qt::Vertical) { | ||
1704 | if (!inBounds(popupRect, Qt::TopEdge)) { | 2004 | if (!inBounds(popupRect, Qt::TopEdge)) { | ||
1705 | popupRect.moveTop(bounds.top()); | 2005 | popupRect.moveTop(bounds.top()); | ||
1706 | } | 2006 | } | ||
1707 | if (!inBounds(popupRect, Qt::BottomEdge)) { | 2007 | if (!inBounds(popupRect, Qt::BottomEdge)) { | ||
1708 | popupRect.moveBottom(bounds.bottom()); | 2008 | popupRect.moveBottom(bounds.bottom()); | ||
1709 | } | 2009 | } | ||
1710 | } | 2010 | } | ||
1711 | if (constraintAdjustments & PositionerConstraint::ResizeY) { | 2011 | if (positioner.resizeConstraintAdjustments() & Qt::Vertical) { | ||
1712 | QRect unconstrainedRect = popupRect; | 2012 | QRect unconstrainedRect = popupRect; | ||
1713 | 2013 | | |||
1714 | if (!inBounds(unconstrainedRect, Qt::TopEdge)) { | 2014 | if (!inBounds(unconstrainedRect, Qt::TopEdge)) { | ||
1715 | unconstrainedRect.setTop(bounds.top()); | 2015 | unconstrainedRect.setTop(bounds.top()); | ||
1716 | } | 2016 | } | ||
1717 | if (!inBounds(unconstrainedRect, Qt::BottomEdge)) { | 2017 | if (!inBounds(unconstrainedRect, Qt::BottomEdge)) { | ||
1718 | unconstrainedRect.setBottom(bounds.bottom()); | 2018 | unconstrainedRect.setBottom(bounds.bottom()); | ||
1719 | } | 2019 | } | ||
1720 | 2020 | | |||
1721 | if (unconstrainedRect.isValid()) { | 2021 | if (unconstrainedRect.isValid()) { | ||
1722 | popupRect = unconstrainedRect; | 2022 | popupRect = unconstrainedRect; | ||
1723 | } | 2023 | } | ||
1724 | } | 2024 | } | ||
1725 | 2025 | | |||
1726 | return popupRect; | 2026 | return popupRect; | ||
1727 | } | 2027 | } | ||
1728 | 2028 | | |||
1729 | QPoint XdgShellClient::popupOffset(const QRect &anchorRect, const Qt::Edges anchorEdge, const Qt::Edges gravity, const QSize popupSize) const | 2029 | bool XdgPopupClient::isCloseable() const | ||
1730 | { | | |||
1731 | QPoint anchorPoint; | | |||
1732 | switch (anchorEdge & (Qt::LeftEdge | Qt::RightEdge)) { | | |||
1733 | case Qt::LeftEdge: | | |||
1734 | anchorPoint.setX(anchorRect.x()); | | |||
1735 | break; | | |||
1736 | case Qt::RightEdge: | | |||
1737 | anchorPoint.setX(anchorRect.x() + anchorRect.width()); | | |||
1738 | break; | | |||
1739 | default: | | |||
1740 | anchorPoint.setX(qRound(anchorRect.x() + anchorRect.width() / 2.0)); | | |||
1741 | } | | |||
1742 | switch (anchorEdge & (Qt::TopEdge | Qt::BottomEdge)) { | | |||
1743 | case Qt::TopEdge: | | |||
1744 | anchorPoint.setY(anchorRect.y()); | | |||
1745 | break; | | |||
1746 | case Qt::BottomEdge: | | |||
1747 | anchorPoint.setY(anchorRect.y() + anchorRect.height()); | | |||
1748 | break; | | |||
1749 | default: | | |||
1750 | anchorPoint.setY(qRound(anchorRect.y() + anchorRect.height() / 2.0)); | | |||
1751 | } | | |||
1752 | | ||||
1753 | // calculate where the top left point of the popup will end up with the applied gravity | | |||
1754 | // gravity indicates direction. i.e if gravitating towards the top the popup's bottom edge | | |||
1755 | // will next to the anchor point | | |||
1756 | QPoint popupPosAdjust; | | |||
1757 | switch (gravity & (Qt::LeftEdge | Qt::RightEdge)) { | | |||
1758 | case Qt::LeftEdge: | | |||
1759 | popupPosAdjust.setX(-popupSize.width()); | | |||
1760 | break; | | |||
1761 | case Qt::RightEdge: | | |||
1762 | popupPosAdjust.setX(0); | | |||
1763 | break; | | |||
1764 | default: | | |||
1765 | popupPosAdjust.setX(qRound(-popupSize.width() / 2.0)); | | |||
1766 | } | | |||
1767 | switch (gravity & (Qt::TopEdge | Qt::BottomEdge)) { | | |||
1768 | case Qt::TopEdge: | | |||
1769 | popupPosAdjust.setY(-popupSize.height()); | | |||
1770 | break; | | |||
1771 | case Qt::BottomEdge: | | |||
1772 | popupPosAdjust.setY(0); | | |||
1773 | break; | | |||
1774 | default: | | |||
1775 | popupPosAdjust.setY(qRound(-popupSize.height() / 2.0)); | | |||
1776 | } | | |||
1777 | | ||||
1778 | return anchorPoint + popupPosAdjust; | | |||
1779 | } | | |||
1780 | | ||||
1781 | void XdgShellClient::doResizeSync() | | |||
1782 | { | 2030 | { | ||
1783 | requestGeometry(moveResizeGeometry()); | 2031 | return false; | ||
1784 | } | 2032 | } | ||
1785 | 2033 | | |||
1786 | QMatrix4x4 XdgShellClient::inputTransformation() const | 2034 | void XdgPopupClient::closeWindow() | ||
1787 | { | 2035 | { | ||
1788 | QMatrix4x4 matrix; | | |||
1789 | matrix.translate(-m_bufferGeometry.x(), -m_bufferGeometry.y()); | | |||
1790 | return matrix; | | |||
1791 | } | 2036 | } | ||
1792 | 2037 | | |||
1793 | void XdgShellClient::installServerSideDecoration(KWaylandServer::ServerSideDecorationInterface *deco) | 2038 | void XdgPopupClient::updateColorScheme() | ||
1794 | { | 2039 | { | ||
1795 | if (m_serverDecoration == deco) { | 2040 | AbstractClient::updateColorScheme(QString()); | ||
1796 | return; | | |||
1797 | } | | |||
1798 | m_serverDecoration = deco; | | |||
1799 | connect(m_serverDecoration, &ServerSideDecorationInterface::destroyed, this, | | |||
1800 | [this] { | | |||
1801 | m_serverDecoration = nullptr; | | |||
1802 | if (m_closing || !Workspace::self()) { | | |||
1803 | return; | | |||
1804 | } | | |||
1805 | if (!m_unmapped) { | | |||
1806 | // maybe delay to next event cycle in case the XdgShellClient is getting destroyed, too | | |||
1807 | updateDecoration(true); | | |||
1808 | } | | |||
1809 | } | | |||
1810 | ); | | |||
1811 | if (!m_unmapped) { | | |||
1812 | updateDecoration(true); | | |||
1813 | } | | |||
1814 | connect(m_serverDecoration, &ServerSideDecorationInterface::modeRequested, this, | | |||
1815 | [this] (ServerSideDecorationManagerInterface::Mode mode) { | | |||
1816 | const bool changed = mode != m_serverDecoration->mode(); | | |||
1817 | if (changed && !m_unmapped) { | | |||
1818 | updateDecoration(false); | | |||
1819 | } | | |||
1820 | } | | |||
1821 | ); | | |||
1822 | } | 2041 | } | ||
1823 | 2042 | | |||
1824 | void XdgShellClient::installXdgDecoration(XdgDecorationInterface *deco) | 2043 | bool XdgPopupClient::noBorder() const | ||
1825 | { | 2044 | { | ||
1826 | Q_ASSERT(m_xdgShellToplevel); | 2045 | return true; | ||
1827 | | ||||
1828 | m_xdgDecoration = deco; | | |||
1829 | | ||||
1830 | connect(m_xdgDecoration, &QObject::destroyed, this, | | |||
1831 | [this] { | | |||
1832 | m_xdgDecoration = nullptr; | | |||
1833 | if (m_closing || !Workspace::self()) { | | |||
1834 | return; | | |||
1835 | } | | |||
1836 | updateDecoration(true); | | |||
1837 | } | | |||
1838 | ); | | |||
1839 | | ||||
1840 | connect(m_xdgDecoration, &XdgDecorationInterface::modeRequested, this, | | |||
1841 | [this] () { | | |||
1842 | //force is true as we must send a new configure response | | |||
1843 | updateDecoration(false, true); | | |||
1844 | }); | | |||
1845 | } | 2046 | } | ||
1846 | 2047 | | |||
1847 | bool XdgShellClient::shouldExposeToWindowManagement() | 2048 | bool XdgPopupClient::userCanSetNoBorder() const | ||
1848 | { | 2049 | { | ||
1849 | if (isLockScreen()) { | | |||
1850 | return false; | 2050 | return false; | ||
1851 | } | 2051 | } | ||
1852 | if (m_xdgShellPopup) { | | |||
1853 | return false; | | |||
1854 | } | | |||
1855 | return true; | | |||
1856 | } | | |||
1857 | 2052 | | |||
1858 | KWaylandServer::XdgShellSurfaceInterface::States XdgShellClient::xdgSurfaceStates() const | 2053 | void XdgPopupClient::setNoBorder(bool set) | ||
1859 | { | 2054 | { | ||
1860 | XdgShellSurfaceInterface::States states; | 2055 | Q_UNUSED(set) | ||
1861 | if (isActive()) { | | |||
1862 | states |= XdgShellSurfaceInterface::State::Activated; | | |||
1863 | } | | |||
1864 | if (isFullScreen()) { | | |||
1865 | states |= XdgShellSurfaceInterface::State::Fullscreen; | | |||
1866 | } | | |||
1867 | if (m_requestedMaximizeMode == MaximizeMode::MaximizeFull) { | | |||
1868 | states |= XdgShellSurfaceInterface::State::Maximized; | | |||
1869 | } | | |||
1870 | if (isResize()) { | | |||
1871 | states |= XdgShellSurfaceInterface::State::Resizing; | | |||
1872 | } | | |||
1873 | return states; | | |||
1874 | } | 2056 | } | ||
1875 | 2057 | | |||
1876 | void XdgShellClient::doMinimize() | 2058 | void XdgPopupClient::updateDecoration(bool check_workspace_pos, bool force) | ||
1877 | { | 2059 | { | ||
1878 | if (isMinimized()) { | 2060 | Q_UNUSED(check_workspace_pos) | ||
1879 | workspace()->clientHidden(this); | 2061 | Q_UNUSED(force) | ||
1880 | } else { | | |||
1881 | emit windowShown(this); | | |||
1882 | } | | |||
1883 | workspace()->updateMinimizedOfTransients(this); | | |||
1884 | } | 2062 | } | ||
1885 | 2063 | | |||
1886 | void XdgShellClient::showOnScreenEdge() | 2064 | void XdgPopupClient::showOnScreenEdge() | ||
1887 | { | 2065 | { | ||
1888 | if (!m_plasmaShellSurface || m_unmapped) { | | |||
1889 | return; | | |||
1890 | } | | |||
1891 | hideClient(false); | | |||
1892 | workspace()->raiseClient(this); | | |||
1893 | if (m_plasmaShellSurface->panelBehavior() == PlasmaShellSurfaceInterface::PanelBehavior::AutoHide) { | | |||
1894 | m_plasmaShellSurface->showAutoHidingPanel(); | | |||
1895 | } | | |||
1896 | } | 2066 | } | ||
1897 | 2067 | | |||
1898 | bool XdgShellClient::dockWantsInput() const | 2068 | bool XdgPopupClient::supportsWindowRules() const | ||
1899 | { | 2069 | { | ||
1900 | if (m_plasmaShellSurface) { | | |||
1901 | if (m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Panel) { | | |||
1902 | return m_plasmaShellSurface->panelTakesFocus(); | | |||
1903 | } | | |||
1904 | } | | |||
1905 | return false; | 2070 | return false; | ||
1906 | } | 2071 | } | ||
1907 | 2072 | | |||
1908 | void XdgShellClient::killWindow() | 2073 | bool XdgPopupClient::wantsInput() const | ||
1909 | { | | |||
1910 | if (!surface()) { | | |||
1911 | return; | | |||
1912 | } | | |||
1913 | auto c = surface()->client(); | | |||
1914 | if (c->processId() == getpid() || c->processId() == 0) { | | |||
1915 | c->destroy(); | | |||
1916 | return; | | |||
1917 | } | | |||
1918 | ::kill(c->processId(), SIGTERM); | | |||
1919 | // give it time to terminate and only if terminate fails, try destroy Wayland connection | | |||
1920 | QTimer::singleShot(5000, c, &ClientConnection::destroy); | | |||
1921 | } | | |||
1922 | | ||||
1923 | bool XdgShellClient::isLocalhost() const | | |||
1924 | { | | |||
1925 | return true; | | |||
1926 | } | | |||
1927 | | ||||
1928 | bool XdgShellClient::hasPopupGrab() const | | |||
1929 | { | | |||
1930 | return m_hasPopupGrab; | | |||
1931 | } | | |||
1932 | | ||||
1933 | void XdgShellClient::popupDone() | | |||
1934 | { | 2074 | { | ||
1935 | if (m_xdgShellPopup) { | 2075 | return false; | ||
1936 | m_xdgShellPopup->popupDone(); | | |||
1937 | } | | |||
1938 | } | 2076 | } | ||
1939 | 2077 | | |||
1940 | void XdgShellClient::updateClientOutputs() | 2078 | void XdgPopupClient::takeFocus() | ||
1941 | { | 2079 | { | ||
1942 | QVector<OutputInterface *> clientOutputs; | | |||
1943 | const auto outputs = waylandServer()->display()->outputs(); | | |||
1944 | for (OutputInterface *output : outputs) { | | |||
1945 | const QRect outputGeometry(output->globalPosition(), output->pixelSize() / output->scale()); | | |||
1946 | if (frameGeometry().intersects(outputGeometry)) { | | |||
1947 | clientOutputs << output; | | |||
1948 | } | | |||
1949 | } | | |||
1950 | surface()->setOutputs(clientOutputs); | | |||
1951 | } | 2080 | } | ||
1952 | 2081 | | |||
1953 | bool XdgShellClient::isPopupWindow() const | 2082 | bool XdgPopupClient::acceptsFocus() const | ||
1954 | { | 2083 | { | ||
1955 | if (Toplevel::isPopupWindow()) { | | |||
1956 | return true; | | |||
1957 | } | | |||
1958 | if (m_xdgShellPopup != nullptr) { | | |||
1959 | return true; | | |||
1960 | } | | |||
1961 | return false; | 2084 | return false; | ||
1962 | } | 2085 | } | ||
1963 | 2086 | | |||
1964 | bool XdgShellClient::supportsWindowRules() const | 2087 | XdgSurfaceConfigure *XdgPopupClient::sendRoleConfigure() const | ||
1965 | { | 2088 | { | ||
1966 | if (m_plasmaShellSurface) { | 2089 | const QPoint parentPosition = transientFor()->framePosToClientPos(transientFor()->pos()); | ||
1967 | return false; | 2090 | const QPoint popupPosition = requestedPos() - parentPosition; | ||
1968 | } | | |||
1969 | return m_xdgShellToplevel; | | |||
1970 | } | | |||
1971 | 2091 | | |||
1972 | QRect XdgShellClient::adjustMoveGeometry(const QRect &rect) const | 2092 | const quint32 serial = m_shellSurface->sendConfigure(QRect(popupPosition, requestedClientSize())); | ||
1973 | { | | |||
1974 | QRect geometry = rect; | | |||
1975 | geometry.moveTopLeft(moveResizeGeometry().topLeft()); | | |||
1976 | return geometry; | | |||
1977 | } | | |||
1978 | 2093 | | |||
1979 | QRect XdgShellClient::adjustResizeGeometry(const QRect &rect) const | 2094 | XdgSurfaceConfigure *configureEvent = new XdgSurfaceConfigure(); | ||
1980 | { | 2095 | configureEvent->position = requestedPos(); | ||
1981 | QRect geometry = rect; | 2096 | configureEvent->size = requestedSize(); | ||
2097 | configureEvent->serial = serial; | ||||
1982 | 2098 | | |||
1983 | // We need to adjust frame geometry because configure events carry the maximum window geometry | 2099 | return configureEvent; | ||
1984 | // size. A client that has aspect ratio can attach a buffer with smaller size than the one in | | |||
1985 | // a configure event. | | |||
1986 | switch (moveResizePointerMode()) { | | |||
1987 | case PositionTopLeft: | | |||
1988 | geometry.moveRight(moveResizeGeometry().right()); | | |||
1989 | geometry.moveBottom(moveResizeGeometry().bottom()); | | |||
1990 | break; | | |||
1991 | case PositionTop: | | |||
1992 | case PositionTopRight: | | |||
1993 | geometry.moveLeft(moveResizeGeometry().left()); | | |||
1994 | geometry.moveBottom(moveResizeGeometry().bottom()); | | |||
1995 | break; | | |||
1996 | case PositionRight: | | |||
1997 | case PositionBottomRight: | | |||
1998 | case PositionBottom: | | |||
1999 | geometry.moveLeft(moveResizeGeometry().left()); | | |||
2000 | geometry.moveTop(moveResizeGeometry().top()); | | |||
2001 | break; | | |||
2002 | case PositionBottomLeft: | | |||
2003 | case PositionLeft: | | |||
2004 | geometry.moveRight(moveResizeGeometry().right()); | | |||
2005 | geometry.moveTop(moveResizeGeometry().top()); | | |||
2006 | break; | | |||
2007 | case PositionCenter: | | |||
2008 | Q_UNREACHABLE(); | | |||
2009 | } | 2100 | } | ||
2010 | 2101 | | |||
2011 | return geometry; | 2102 | void XdgPopupClient::handleGrabRequested(SeatInterface *seat, quint32 serial) | ||
2103 | { | ||||
2104 | Q_UNUSED(seat) | ||||
2105 | Q_UNUSED(serial) | ||||
2106 | m_haveExplicitGrab = true; | ||||
2012 | } | 2107 | } | ||
2013 | 2108 | | |||
2014 | void XdgShellClient::ping(PingReason reason) | 2109 | void XdgPopupClient::initialize() | ||
2015 | { | 2110 | { | ||
2016 | Q_ASSERT(m_xdgShellToplevel); | 2111 | const QRect area = workspace()->clientArea(PlacementArea, Screens::self()->current(), desktop()); | ||
2112 | placeIn(area); | ||||
2017 | 2113 | | |||
2018 | XdgShellInterface *shell = static_cast<XdgShellInterface *>(m_xdgShellToplevel->global()); | 2114 | scheduleConfigure(); | ||
2019 | const quint32 serial = shell->ping(m_xdgShellToplevel); | | |||
2020 | m_pingSerials.insert(serial, reason); | | |||
2021 | } | 2115 | } | ||
2022 | 2116 | | |||
2023 | } | 2117 | } // namespace KWin |
We leak the m_lastAckedConfigure if someone calls ackConfigure and gets deleted before commit.
I'm guessing you didn't use unique_ptr because of the QQueue, but storing via a qsharedpointer would work nicely.