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