File indexing completed on 2024-05-19 09:25:45

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/clientconnection.h"
0012 #include "wayland/display.h"
0013 #include "wayland/surface.h"
0014 #include "wayland_server.h"
0015 #include "workspace.h"
0016 
0017 #include <QFileInfo>
0018 
0019 #include <csignal>
0020 
0021 #include <sys/types.h>
0022 #include <unistd.h>
0023 
0024 namespace KWin
0025 {
0026 
0027 enum WaylandGeometryType {
0028     WaylandGeometryClient = 0x1,
0029     WaylandGeometryFrame = 0x2,
0030     WaylandGeometryBuffer = 0x4,
0031 };
0032 Q_DECLARE_FLAGS(WaylandGeometryTypes, WaylandGeometryType)
0033 
0034 WaylandWindow::WaylandWindow(SurfaceInterface *surface)
0035     : m_isScreenLocker(surface->client() == waylandServer()->screenLockerClientConnection())
0036 {
0037     setSurface(surface);
0038 
0039     connect(surface, &SurfaceInterface::shadowChanged,
0040             this, &WaylandWindow::updateShadow);
0041     connect(this, &WaylandWindow::frameGeometryChanged,
0042             this, &WaylandWindow::updateClientOutputs);
0043     connect(this, &WaylandWindow::desktopFileNameChanged,
0044             this, &WaylandWindow::updateIcon);
0045     connect(workspace(), &Workspace::outputsChanged, this, &WaylandWindow::updateClientOutputs);
0046 
0047     updateResourceName();
0048     updateIcon();
0049 }
0050 
0051 std::unique_ptr<WindowItem> WaylandWindow::createItem(Scene *scene)
0052 {
0053     return std::make_unique<WindowItemWayland>(this, scene);
0054 }
0055 
0056 QString WaylandWindow::captionNormal() const
0057 {
0058     return m_captionNormal;
0059 }
0060 
0061 QString WaylandWindow::captionSuffix() const
0062 {
0063     return m_captionSuffix;
0064 }
0065 
0066 pid_t WaylandWindow::pid() const
0067 {
0068     return surface() ? surface()->client()->processId() : -1;
0069 }
0070 
0071 bool WaylandWindow::isClient() const
0072 {
0073     return true;
0074 }
0075 
0076 bool WaylandWindow::isLockScreen() const
0077 {
0078     return m_isScreenLocker;
0079 }
0080 
0081 bool WaylandWindow::isLocalhost() const
0082 {
0083     return true;
0084 }
0085 
0086 Window *WaylandWindow::findModal(bool allow_itself)
0087 {
0088     return nullptr;
0089 }
0090 
0091 QRectF WaylandWindow::resizeWithChecks(const QRectF &geometry, const QSizeF &size)
0092 {
0093     const QRectF area = workspace()->clientArea(WorkArea, this, geometry.center());
0094 
0095     qreal width = size.width();
0096     qreal height = size.height();
0097 
0098     // don't allow growing larger than workarea
0099     if (width > area.width()) {
0100         width = area.width();
0101     }
0102     if (height > area.height()) {
0103         height = area.height();
0104     }
0105     return QRectF(geometry.topLeft(), QSizeF(width, height));
0106 }
0107 
0108 void WaylandWindow::killWindow()
0109 {
0110     if (!surface()) {
0111         return;
0112     }
0113     auto c = surface()->client();
0114     if (c->processId() == getpid() || c->processId() == 0) {
0115         c->destroy();
0116         return;
0117     }
0118     ::kill(c->processId(), SIGTERM);
0119     // give it time to terminate and only if terminate fails, try destroy Wayland connection
0120     QTimer::singleShot(5000, c, &ClientConnection::destroy);
0121 }
0122 
0123 QString WaylandWindow::windowRole() const
0124 {
0125     return QString();
0126 }
0127 
0128 bool WaylandWindow::belongsToSameApplication(const Window *other, SameApplicationChecks checks) const
0129 {
0130     if (checks.testFlag(SameApplicationCheck::AllowCrossProcesses)) {
0131         if (other->desktopFileName() == desktopFileName()) {
0132             return true;
0133         }
0134     }
0135     if (auto s = other->surface()) {
0136         return s->client() == surface()->client();
0137     }
0138     return false;
0139 }
0140 
0141 bool WaylandWindow::belongsToDesktop() const
0142 {
0143     const auto clients = waylandServer()->windows();
0144 
0145     return std::any_of(clients.constBegin(), clients.constEnd(),
0146                        [this](const Window *client) {
0147                            if (belongsToSameApplication(client, SameApplicationChecks())) {
0148                                return client->isDesktop();
0149                            }
0150                            return false;
0151                        });
0152 }
0153 
0154 void WaylandWindow::updateClientOutputs()
0155 {
0156     if (isDeleted()) {
0157         return;
0158     }
0159     surface()->setOutputs(waylandServer()->display()->outputsIntersecting(frameGeometry().toAlignedRect()),
0160                           waylandServer()->display()->largestIntersectingOutput(frameGeometry().toAlignedRect()));
0161     if (output()) {
0162         surface()->setPreferredBufferScale(output()->scale());
0163         surface()->setPreferredBufferTransform(output()->transform());
0164         surface()->setPreferredColorDescription(output()->colorDescription());
0165     }
0166 }
0167 
0168 void WaylandWindow::updateIcon()
0169 {
0170     const QString waylandIconName = QStringLiteral("wayland");
0171     const QString dfIconName = iconFromDesktopFile();
0172     const QString iconName = dfIconName.isEmpty() ? waylandIconName : dfIconName;
0173     if (iconName == icon().name()) {
0174         return;
0175     }
0176     setIcon(QIcon::fromTheme(iconName));
0177 }
0178 
0179 void WaylandWindow::updateResourceName()
0180 {
0181     const QFileInfo fileInfo(surface()->client()->executablePath());
0182     if (fileInfo.exists()) {
0183         const QByteArray executableFileName = fileInfo.fileName().toUtf8();
0184         setResourceClass(executableFileName, executableFileName);
0185     }
0186 }
0187 
0188 void WaylandWindow::updateCaption()
0189 {
0190     const QString oldSuffix = m_captionSuffix;
0191     const auto shortcut = shortcutCaptionSuffix();
0192     m_captionSuffix = shortcut;
0193     if ((!isSpecialWindow() || isToolbar()) && findWindowWithSameCaption()) {
0194         int i = 2;
0195         do {
0196             m_captionSuffix = shortcut + QLatin1String(" <") + QString::number(i) + QLatin1Char('>');
0197             i++;
0198         } while (findWindowWithSameCaption());
0199     }
0200     if (m_captionSuffix != oldSuffix) {
0201         Q_EMIT captionChanged();
0202     }
0203 }
0204 
0205 void WaylandWindow::setCaption(const QString &caption)
0206 {
0207     const QString oldNormal = m_captionNormal;
0208     const QString oldSuffix = m_captionSuffix;
0209 
0210     m_captionNormal = caption.simplified();
0211     updateCaption();
0212 
0213     if (m_captionNormal != oldNormal) {
0214         Q_EMIT captionNormalChanged();
0215     }
0216     if (m_captionSuffix == oldSuffix) {
0217         // Don't emit caption change twice it already got emitted by the changing suffix.
0218         Q_EMIT captionChanged();
0219     }
0220 }
0221 
0222 void WaylandWindow::doSetActive()
0223 {
0224     if (isActive()) { // TODO: Xwayland clients must be unfocused somewhere else.
0225         StackingUpdatesBlocker blocker(workspace());
0226         workspace()->focusToNull();
0227     }
0228 }
0229 
0230 void WaylandWindow::cleanGrouping()
0231 {
0232     // We want to break parent-child relationships, but preserve stacking
0233     // order constraints at the same time for window closing animations.
0234 
0235     if (transientFor()) {
0236         transientFor()->removeTransientFromList(this);
0237         setTransientFor(nullptr);
0238     }
0239 
0240     const auto children = transients();
0241     for (Window *transient : children) {
0242         removeTransientFromList(transient);
0243         transient->setTransientFor(nullptr);
0244     }
0245 }
0246 
0247 QRectF WaylandWindow::frameRectToBufferRect(const QRectF &rect) const
0248 {
0249     return QRectF(rect.topLeft(), surface()->size());
0250 }
0251 
0252 void WaylandWindow::updateGeometry(const QRectF &rect)
0253 {
0254     const QRectF oldClientGeometry = m_clientGeometry;
0255     const QRectF oldFrameGeometry = m_frameGeometry;
0256     const QRectF oldBufferGeometry = m_bufferGeometry;
0257     const Output *oldOutput = m_output;
0258 
0259     m_clientGeometry = frameRectToClientRect(rect);
0260     m_frameGeometry = rect;
0261     m_bufferGeometry = frameRectToBufferRect(rect);
0262 
0263     WaylandGeometryTypes changedGeometries;
0264 
0265     if (m_clientGeometry != oldClientGeometry) {
0266         changedGeometries |= WaylandGeometryClient;
0267     }
0268     if (m_frameGeometry != oldFrameGeometry) {
0269         changedGeometries |= WaylandGeometryFrame;
0270     }
0271     if (m_bufferGeometry != oldBufferGeometry) {
0272         changedGeometries |= WaylandGeometryBuffer;
0273     }
0274 
0275     if (!changedGeometries) {
0276         return;
0277     }
0278 
0279     m_output = workspace()->outputAt(rect.center());
0280     updateWindowRules(Rules::Position | Rules::Size);
0281 
0282     if (changedGeometries & WaylandGeometryBuffer) {
0283         Q_EMIT bufferGeometryChanged(oldBufferGeometry);
0284     }
0285     if (changedGeometries & WaylandGeometryClient) {
0286         Q_EMIT clientGeometryChanged(oldClientGeometry);
0287     }
0288     if (changedGeometries & WaylandGeometryFrame) {
0289         Q_EMIT frameGeometryChanged(oldFrameGeometry);
0290     }
0291     if (oldOutput != m_output) {
0292         Q_EMIT outputChanged();
0293     }
0294 }
0295 
0296 void WaylandWindow::markAsMapped()
0297 {
0298     if (Q_UNLIKELY(!ready_for_painting)) {
0299         setupCompositing();
0300         updateCaption();
0301         setReadyForPainting();
0302     }
0303 }
0304 
0305 } // namespace KWin
0306 
0307 #include "moc_waylandwindow.cpp"