File indexing completed on 2024-04-28 16:49:06

0001 /*
0002     SPDX-FileCopyrightText: 2015 Martin Flöser <mgraesslin@kde.org>
0003     SPDX-FileCopyrightText: 2018 David Edmundson <davidedmundson@kde.org>
0004     SPDX-FileCopyrightText: 2020 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
0005 
0006     SPDX-License-Identifier: GPL-2.0-or-later
0007 */
0008 
0009 #include "waylandwindow.h"
0010 #include "scene/windowitem.h"
0011 #include "wayland/clientbuffer.h"
0012 #include "wayland/clientconnection.h"
0013 #include "wayland/display.h"
0014 #include "wayland/surface_interface.h"
0015 #include "wayland_server.h"
0016 #include "workspace.h"
0017 
0018 #include <QFileInfo>
0019 
0020 #include <csignal>
0021 
0022 #include <sys/types.h>
0023 #include <unistd.h>
0024 
0025 using namespace KWaylandServer;
0026 
0027 namespace KWin
0028 {
0029 
0030 enum WaylandGeometryType {
0031     WaylandGeometryClient = 0x1,
0032     WaylandGeometryFrame = 0x2,
0033     WaylandGeometryBuffer = 0x4,
0034 };
0035 Q_DECLARE_FLAGS(WaylandGeometryTypes, WaylandGeometryType)
0036 
0037 WaylandWindow::WaylandWindow(SurfaceInterface *surface)
0038 {
0039     setSurface(surface);
0040     setupCompositing();
0041 
0042     connect(surface, &SurfaceInterface::shadowChanged,
0043             this, &WaylandWindow::updateShadow);
0044     connect(this, &WaylandWindow::frameGeometryChanged,
0045             this, &WaylandWindow::updateClientOutputs);
0046     connect(this, &WaylandWindow::desktopFileNameChanged,
0047             this, &WaylandWindow::updateIcon);
0048     connect(workspace(), &Workspace::outputsChanged, this, &WaylandWindow::updateClientOutputs);
0049     connect(surface->client(), &ClientConnection::aboutToBeDestroyed,
0050             this, &WaylandWindow::destroyWindow);
0051 
0052     updateResourceName();
0053     updateIcon();
0054 }
0055 
0056 std::unique_ptr<WindowItem> WaylandWindow::createItem(Scene *scene)
0057 {
0058     return std::make_unique<WindowItemWayland>(this, scene);
0059 }
0060 
0061 QString WaylandWindow::captionNormal() const
0062 {
0063     return m_captionNormal;
0064 }
0065 
0066 QString WaylandWindow::captionSuffix() const
0067 {
0068     return m_captionSuffix;
0069 }
0070 
0071 pid_t WaylandWindow::pid() const
0072 {
0073     return surface()->client()->processId();
0074 }
0075 
0076 bool WaylandWindow::isClient() const
0077 {
0078     return true;
0079 }
0080 
0081 bool WaylandWindow::isLockScreen() const
0082 {
0083     return surface()->client() == waylandServer()->screenLockerClientConnection();
0084 }
0085 
0086 bool WaylandWindow::isLocalhost() const
0087 {
0088     return true;
0089 }
0090 
0091 Window *WaylandWindow::findModal(bool allow_itself)
0092 {
0093     return nullptr;
0094 }
0095 
0096 QRectF WaylandWindow::resizeWithChecks(const QRectF &geometry, const QSizeF &size)
0097 {
0098     const QRectF area = workspace()->clientArea(WorkArea, this, geometry.center());
0099 
0100     qreal width = size.width();
0101     qreal height = size.height();
0102 
0103     // don't allow growing larger than workarea
0104     if (width > area.width()) {
0105         width = area.width();
0106     }
0107     if (height > area.height()) {
0108         height = area.height();
0109     }
0110     return QRectF(geometry.topLeft(), QSizeF(width, height));
0111 }
0112 
0113 void WaylandWindow::killWindow()
0114 {
0115     if (!surface()) {
0116         return;
0117     }
0118     auto c = surface()->client();
0119     if (c->processId() == getpid() || c->processId() == 0) {
0120         c->destroy();
0121         return;
0122     }
0123     ::kill(c->processId(), SIGTERM);
0124     // give it time to terminate and only if terminate fails, try destroy Wayland connection
0125     QTimer::singleShot(5000, c, &ClientConnection::destroy);
0126 }
0127 
0128 QString WaylandWindow::windowRole() const
0129 {
0130     return QString();
0131 }
0132 
0133 bool WaylandWindow::belongsToSameApplication(const Window *other, SameApplicationChecks checks) const
0134 {
0135     if (checks.testFlag(SameApplicationCheck::AllowCrossProcesses)) {
0136         if (other->desktopFileName() == desktopFileName()) {
0137             return true;
0138         }
0139     }
0140     if (auto s = other->surface()) {
0141         return s->client() == surface()->client();
0142     }
0143     return false;
0144 }
0145 
0146 bool WaylandWindow::belongsToDesktop() const
0147 {
0148     const auto clients = waylandServer()->windows();
0149 
0150     return std::any_of(clients.constBegin(), clients.constEnd(),
0151                        [this](const Window *client) {
0152                            if (belongsToSameApplication(client, SameApplicationChecks())) {
0153                                return client->isDesktop();
0154                            }
0155                            return false;
0156                        });
0157 }
0158 
0159 void WaylandWindow::updateClientOutputs()
0160 {
0161     surface()->setOutputs(waylandServer()->display()->outputsIntersecting(frameGeometry().toAlignedRect()));
0162     if (output()) {
0163         surface()->setPreferredScale(output()->scale());
0164     }
0165 }
0166 
0167 void WaylandWindow::updateIcon()
0168 {
0169     const QString waylandIconName = QStringLiteral("wayland");
0170     const QString dfIconName = iconFromDesktopFile();
0171     const QString iconName = dfIconName.isEmpty() ? waylandIconName : dfIconName;
0172     if (iconName == icon().name()) {
0173         return;
0174     }
0175     setIcon(QIcon::fromTheme(iconName));
0176 }
0177 
0178 void WaylandWindow::updateResourceName()
0179 {
0180     const QFileInfo fileInfo(surface()->client()->executablePath());
0181     if (fileInfo.exists()) {
0182         const QByteArray executableFileName = fileInfo.fileName().toUtf8();
0183         setResourceClass(executableFileName, executableFileName);
0184     }
0185 }
0186 
0187 void WaylandWindow::updateCaption()
0188 {
0189     const QString oldSuffix = m_captionSuffix;
0190     const auto shortcut = shortcutCaptionSuffix();
0191     m_captionSuffix = shortcut;
0192     if ((!isSpecialWindow() || isToolbar()) && findWindowWithSameCaption()) {
0193         int i = 2;
0194         do {
0195             m_captionSuffix = shortcut + QLatin1String(" <") + QString::number(i) + QLatin1Char('>');
0196             i++;
0197         } while (findWindowWithSameCaption());
0198     }
0199     if (m_captionSuffix != oldSuffix) {
0200         Q_EMIT captionChanged();
0201     }
0202 }
0203 
0204 void WaylandWindow::setCaption(const QString &caption)
0205 {
0206     const QString oldSuffix = m_captionSuffix;
0207     m_captionNormal = caption.simplified();
0208     updateCaption();
0209     if (m_captionSuffix == oldSuffix) {
0210         // Don't emit caption change twice it already got emitted by the changing suffix.
0211         Q_EMIT captionChanged();
0212     }
0213 }
0214 
0215 void WaylandWindow::doSetActive()
0216 {
0217     if (isActive()) { // TODO: Xwayland clients must be unfocused somewhere else.
0218         StackingUpdatesBlocker blocker(workspace());
0219         workspace()->focusToNull();
0220     }
0221 }
0222 
0223 void WaylandWindow::updateDepth()
0224 {
0225     if (surface()->buffer()->hasAlphaChannel()) {
0226         setDepth(32);
0227     } else {
0228         setDepth(24);
0229     }
0230 }
0231 
0232 void WaylandWindow::cleanGrouping()
0233 {
0234     if (transientFor()) {
0235         transientFor()->removeTransient(this);
0236     }
0237     for (auto it = transients().constBegin(); it != transients().constEnd();) {
0238         if ((*it)->transientFor() == this) {
0239             removeTransient(*it);
0240             it = transients().constBegin(); // restart, just in case something more has changed with the list
0241         } else {
0242             ++it;
0243         }
0244     }
0245 }
0246 
0247 bool WaylandWindow::isShown() const
0248 {
0249     return !isZombie() && !isHidden() && !isMinimized();
0250 }
0251 
0252 bool WaylandWindow::isHiddenInternal() const
0253 {
0254     return isHidden();
0255 }
0256 
0257 bool WaylandWindow::isHidden() const
0258 {
0259     return m_isHidden;
0260 }
0261 
0262 void WaylandWindow::showClient()
0263 {
0264     if (!isHidden()) {
0265         return;
0266     }
0267     m_isHidden = false;
0268     Q_EMIT windowShown(this);
0269 }
0270 
0271 void WaylandWindow::hideClient()
0272 {
0273     if (isHidden()) {
0274         return;
0275     }
0276     if (isInteractiveMoveResize()) {
0277         leaveInteractiveMoveResize();
0278     }
0279     m_isHidden = true;
0280     workspace()->windowHidden(this);
0281     Q_EMIT windowHidden(this);
0282 }
0283 
0284 QRectF WaylandWindow::frameRectToBufferRect(const QRectF &rect) const
0285 {
0286     return QRectF(rect.topLeft(), surface()->size());
0287 }
0288 
0289 void WaylandWindow::updateGeometry(const QRectF &rect)
0290 {
0291     const QRectF oldClientGeometry = m_clientGeometry;
0292     const QRectF oldFrameGeometry = m_frameGeometry;
0293     const QRectF oldBufferGeometry = m_bufferGeometry;
0294     const Output *oldOutput = m_output;
0295 
0296     m_clientGeometry = frameRectToClientRect(rect);
0297     m_frameGeometry = rect;
0298     m_bufferGeometry = frameRectToBufferRect(rect);
0299 
0300     WaylandGeometryTypes changedGeometries;
0301 
0302     if (m_clientGeometry != oldClientGeometry) {
0303         changedGeometries |= WaylandGeometryClient;
0304     }
0305     if (m_frameGeometry != oldFrameGeometry) {
0306         changedGeometries |= WaylandGeometryFrame;
0307     }
0308     if (m_bufferGeometry != oldBufferGeometry) {
0309         changedGeometries |= WaylandGeometryBuffer;
0310     }
0311 
0312     if (!changedGeometries) {
0313         return;
0314     }
0315 
0316     m_output = workspace()->outputAt(rect.center());
0317     updateWindowRules(Rules::Position | Rules::Size);
0318 
0319     if (changedGeometries & WaylandGeometryBuffer) {
0320         Q_EMIT bufferGeometryChanged(this, oldBufferGeometry);
0321     }
0322     if (changedGeometries & WaylandGeometryClient) {
0323         Q_EMIT clientGeometryChanged(this, oldClientGeometry);
0324     }
0325     if (changedGeometries & WaylandGeometryFrame) {
0326         Q_EMIT frameGeometryChanged(this, oldFrameGeometry);
0327     }
0328     if (oldOutput != m_output) {
0329         Q_EMIT screenChanged();
0330     }
0331     Q_EMIT geometryShapeChanged(this, oldFrameGeometry);
0332 }
0333 
0334 } // namespace KWin