File indexing completed on 2024-05-19 16:35:34

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 2019 Roman Gilg <subdiff@gmail.com>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 #include "clipboard.h"
0010 
0011 #include "datasource.h"
0012 #include "selection_source.h"
0013 
0014 #include "wayland/seat_interface.h"
0015 #include "wayland_server.h"
0016 #include "workspace.h"
0017 #include "x11window.h"
0018 
0019 #include <xcb/xcb_event.h>
0020 #include <xcb/xfixes.h>
0021 
0022 #include <xwayland_logging.h>
0023 
0024 namespace KWin
0025 {
0026 namespace Xwl
0027 {
0028 
0029 Clipboard::Clipboard(xcb_atom_t atom, QObject *parent)
0030     : Selection(atom, parent)
0031 {
0032     xcb_connection_t *xcbConn = kwinApp()->x11Connection();
0033 
0034     const uint32_t clipboardValues[] = {XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE};
0035     xcb_create_window(xcbConn,
0036                       XCB_COPY_FROM_PARENT,
0037                       window(),
0038                       kwinApp()->x11RootWindow(),
0039                       0, 0,
0040                       10, 10,
0041                       0,
0042                       XCB_WINDOW_CLASS_INPUT_OUTPUT,
0043                       XCB_COPY_FROM_PARENT,
0044                       XCB_CW_EVENT_MASK,
0045                       clipboardValues);
0046     registerXfixes();
0047     xcb_flush(xcbConn);
0048 
0049     connect(waylandServer()->seat(), &KWaylandServer::SeatInterface::selectionChanged,
0050             this, &Clipboard::wlSelectionChanged);
0051 }
0052 
0053 void Clipboard::wlSelectionChanged(KWaylandServer::AbstractDataSource *dsi)
0054 {
0055     if (m_waitingForTargets) {
0056         return;
0057     }
0058 
0059     if (!ownsSelection(dsi)) {
0060         // Wayland native window provides new selection
0061         if (!m_checkConnection) {
0062             m_checkConnection = connect(workspace(), &Workspace::windowActivated,
0063                                         this, &Clipboard::checkWlSource);
0064         }
0065         // remove previous source so checkWlSource() can create a new one
0066         setWlSource(nullptr);
0067     }
0068     checkWlSource();
0069 }
0070 
0071 bool Clipboard::ownsSelection(KWaylandServer::AbstractDataSource *dsi) const
0072 {
0073     return dsi && dsi == m_selectionSource.get();
0074 }
0075 
0076 void Clipboard::checkWlSource()
0077 {
0078     if (m_waitingForTargets) {
0079         return;
0080     }
0081 
0082     auto dsi = waylandServer()->seat()->selection();
0083     auto removeSource = [this] {
0084         if (wlSource()) {
0085             setWlSource(nullptr);
0086             ownSelection(false);
0087         }
0088     };
0089 
0090     // Wayland source gets created when:
0091     // - the Wl selection exists,
0092     // - its source is not Xwayland,
0093     // - a window is active,
0094     // - this window is an Xwayland one.
0095     //
0096     // Otherwise the Wayland source gets destroyed to shield
0097     // against snooping X windows.
0098 
0099     if (!dsi || ownsSelection(dsi)) {
0100         // Xwayland source or no source
0101         disconnect(m_checkConnection);
0102         m_checkConnection = QMetaObject::Connection();
0103         removeSource();
0104         return;
0105     }
0106     if (!qobject_cast<KWin::X11Window *>(workspace()->activeWindow())) {
0107         // no active window or active window is Wayland native
0108         removeSource();
0109         return;
0110     }
0111     // Xwayland window is active and we need a Wayland source
0112     if (wlSource()) {
0113         // source already exists, nothing more to do
0114         return;
0115     }
0116     auto *wls = new WlSource(this);
0117     setWlSource(wls);
0118     if (dsi) {
0119         wls->setDataSourceIface(dsi);
0120     }
0121     ownSelection(true);
0122 }
0123 
0124 void Clipboard::doHandleXfixesNotify(xcb_xfixes_selection_notify_event_t *event)
0125 {
0126     const Window *window = workspace()->activeWindow();
0127     if (!qobject_cast<const X11Window *>(window)) {
0128         // clipboard is only allowed to be acquired when Xwayland has focus
0129         // TODO: can we make this stronger (window id comparison)?
0130         createX11Source(nullptr);
0131         return;
0132     }
0133 
0134     createX11Source(event);
0135 
0136     if (X11Source *source = x11Source()) {
0137         source->getTargets();
0138         m_waitingForTargets = true;
0139     } else {
0140         qCWarning(KWIN_XWL) << "Could not create a source from" << event << Qt::hex << (event ? event->owner : -1);
0141     }
0142 }
0143 
0144 void Clipboard::x11OffersChanged(const QStringList &added, const QStringList &removed)
0145 {
0146     m_waitingForTargets = false;
0147     X11Source *source = x11Source();
0148     if (!source) {
0149         qCWarning(KWIN_XWL) << "offers changed when not having an X11Source!?";
0150         return;
0151     }
0152 
0153     const Mimes offers = source->offers();
0154 
0155     if (!offers.isEmpty()) {
0156         QStringList mimeTypes;
0157         mimeTypes.reserve(offers.size());
0158         std::transform(offers.begin(), offers.end(), std::back_inserter(mimeTypes), [](const Mimes::value_type &pair) {
0159             return pair.first;
0160         });
0161         auto newSelection = std::make_unique<XwlDataSource>();
0162         newSelection->setMimeTypes(mimeTypes);
0163         connect(newSelection.get(), &XwlDataSource::dataRequested, source, &X11Source::startTransfer);
0164         // we keep the old selection around because setSelection needs it to be still alive
0165         std::swap(m_selectionSource, newSelection);
0166         waylandServer()->seat()->setSelection(m_selectionSource.get());
0167     } else {
0168         KWaylandServer::AbstractDataSource *currentSelection = waylandServer()->seat()->selection();
0169         if (!ownsSelection(currentSelection)) {
0170             waylandServer()->seat()->setSelection(nullptr);
0171             m_selectionSource.reset();
0172         }
0173     }
0174 }
0175 
0176 } // namespace Xwl
0177 } // namespace KWin