File indexing completed on 2024-05-12 05:32:35

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