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     SPDX-FileCopyrightText: 2021 David Edmundson <davidedmundson@kde.org>
0007     SPDX-FileCopyrightText: 2021 David Redondo <kde@david-redondo.de>
0008     SPDX-License-Identifier: GPL-2.0-or-later
0009 */
0010 #include "dnd.h"
0011 
0012 #include "databridge.h"
0013 #include "drag_wl.h"
0014 #include "drag_x.h"
0015 #include "selection_source.h"
0016 
0017 #include "atoms.h"
0018 #include "wayland/compositor_interface.h"
0019 #include "wayland/datasource_interface.h"
0020 #include "wayland/seat_interface.h"
0021 #include "wayland_server.h"
0022 #include "window.h"
0023 #include "workspace.h"
0024 #include "xwayland.h"
0025 #include "xwldrophandler.h"
0026 
0027 #include <QMouseEvent>
0028 
0029 #include <xcb/xcb.h>
0030 
0031 namespace KWin
0032 {
0033 namespace Xwl
0034 {
0035 
0036 // version of DnD support in X
0037 const static uint32_t s_version = 5;
0038 uint32_t Dnd::version()
0039 {
0040     return s_version;
0041 }
0042 
0043 XwlDropHandler *Dnd::dropHandler() const
0044 {
0045     return m_dropHandler;
0046 }
0047 
0048 Dnd::Dnd(xcb_atom_t atom, QObject *parent)
0049     : Selection(atom, parent)
0050     , m_dropHandler(new XwlDropHandler(this))
0051 {
0052     xcb_connection_t *xcbConn = kwinApp()->x11Connection();
0053 
0054     const uint32_t dndValues[] = {XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE};
0055     xcb_create_window(xcbConn,
0056                       XCB_COPY_FROM_PARENT,
0057                       window(),
0058                       kwinApp()->x11RootWindow(),
0059                       0, 0,
0060                       8192, 8192, // TODO: get current screen size and connect to changes
0061                       0,
0062                       XCB_WINDOW_CLASS_INPUT_OUTPUT,
0063                       XCB_COPY_FROM_PARENT,
0064                       XCB_CW_EVENT_MASK,
0065                       dndValues);
0066     registerXfixes();
0067 
0068     xcb_change_property(xcbConn,
0069                         XCB_PROP_MODE_REPLACE,
0070                         window(),
0071                         atoms->xdnd_aware,
0072                         XCB_ATOM_ATOM,
0073                         32, 1, &s_version);
0074     xcb_flush(xcbConn);
0075 
0076     connect(waylandServer()->seat(), &KWaylandServer::SeatInterface::dragStarted, this, &Dnd::startDrag);
0077     connect(waylandServer()->seat(), &KWaylandServer::SeatInterface::dragEnded, this, &Dnd::endDrag);
0078 }
0079 
0080 void Dnd::doHandleXfixesNotify(xcb_xfixes_selection_notify_event_t *event)
0081 {
0082     if (qobject_cast<XToWlDrag *>(m_currentDrag)) {
0083         // X drag is in progress, rogue X client took over the selection.
0084         return;
0085     }
0086     if (m_currentDrag) {
0087         // Wl drag is in progress - don't overwrite by rogue X client,
0088         // get it back instead!
0089         ownSelection(true);
0090         return;
0091     }
0092     createX11Source(nullptr);
0093     const auto *seat = waylandServer()->seat();
0094     auto *originSurface = seat->focusedPointerSurface();
0095     if (!originSurface) {
0096         return;
0097     }
0098     if (originSurface->client() != waylandServer()->xWaylandConnection()) {
0099         // focused surface client is not Xwayland - do not allow drag to start
0100         // TODO: can we make this stronger (window id comparison)?
0101         return;
0102     }
0103     if (!seat->isPointerButtonPressed(Qt::LeftButton)) {
0104         // we only allow drags to be started on (left) pointer button being
0105         // pressed for now
0106         return;
0107     }
0108     createX11Source(event);
0109     X11Source *source = x11Source();
0110     if (!source) {
0111         return;
0112     }
0113     m_currentDrag = new XToWlDrag(source, this);
0114 }
0115 
0116 void Dnd::x11OffersChanged(const QStringList &added, const QStringList &removed)
0117 {
0118 }
0119 
0120 bool Dnd::handleClientMessage(xcb_client_message_event_t *event)
0121 {
0122     for (Drag *drag : std::as_const(m_oldDrags)) {
0123         if (drag->handleClientMessage(event)) {
0124             return true;
0125         }
0126     }
0127     if (m_currentDrag && m_currentDrag->handleClientMessage(event)) {
0128         return true;
0129     }
0130     return false;
0131 }
0132 
0133 DragEventReply Dnd::dragMoveFilter(Window *target, const QPoint &pos)
0134 {
0135     Q_ASSERT(m_currentDrag);
0136     return m_currentDrag->moveFilter(target, pos);
0137 }
0138 
0139 void Dnd::startDrag()
0140 {
0141     auto dragSource = waylandServer()->seat()->dragSource();
0142     if (qobject_cast<XwlDataSource *>(dragSource)) {
0143         return;
0144     }
0145 
0146     // There can only ever be one Wl native drag at the same time.
0147     Q_ASSERT(!m_currentDrag);
0148 
0149     // New Wl to X drag, init drag and Wl source.
0150     m_currentDrag = new WlToXDrag(this);
0151     auto source = new WlSource(this);
0152     source->setDataSourceIface(dragSource);
0153      connect(dragSource, &KWaylandServer::AbstractDataSource::aboutToBeDestroyed, this, [this, source] {
0154         if (source == wlSource()) {
0155             setWlSource(nullptr);
0156         }
0157     });
0158     setWlSource(source);
0159     ownSelection(true);
0160 }
0161 
0162 void Dnd::endDrag()
0163 {
0164     Q_ASSERT(m_currentDrag);
0165 
0166     connect(m_currentDrag, &Drag::finish, this, &Dnd::clearOldDrag);
0167     m_oldDrags << m_currentDrag;
0168 
0169     m_currentDrag = nullptr;
0170 }
0171 
0172 void Dnd::clearOldDrag(Drag *drag)
0173 {
0174     m_oldDrags.removeOne(drag);
0175     delete drag;
0176 }
0177 
0178 using DnDAction = KWaylandServer::DataDeviceManagerInterface::DnDAction;
0179 using DnDActions = KWaylandServer::DataDeviceManagerInterface::DnDActions;
0180 
0181 DnDAction Dnd::atomToClientAction(xcb_atom_t atom)
0182 {
0183     if (atom == atoms->xdnd_action_copy) {
0184         return DnDAction::Copy;
0185     } else if (atom == atoms->xdnd_action_move) {
0186         return DnDAction::Move;
0187     } else if (atom == atoms->xdnd_action_ask) {
0188         // we currently do not support it - need some test client first
0189         return DnDAction::None;
0190         //        return DnDAction::Ask;
0191     }
0192     return DnDAction::None;
0193 }
0194 
0195 xcb_atom_t Dnd::clientActionToAtom(DnDAction action)
0196 {
0197     if (action == DnDAction::Copy) {
0198         return atoms->xdnd_action_copy;
0199     } else if (action == DnDAction::Move) {
0200         return atoms->xdnd_action_move;
0201     } else if (action == DnDAction::Ask) {
0202         // we currently do not support it - need some test client first
0203         return XCB_ATOM_NONE;
0204         //        return atoms->xdnd_action_ask;
0205     }
0206     return XCB_ATOM_NONE;
0207 }
0208 
0209 } // namespace Xwl
0210 } // namespace KWin