xcb: Use XShape for DnD when a compositing manager is not running
Otherwise transparent areas of the drag'n'drop pixmap are painted with the black color. Task-number: QTBUG-45193 Change-Id: I55b7c7caababe13584fa1c7a52835f112e20f920 Reviewed-by: Lars Knoll <lars.knoll@theqtcompany.com>
This commit is contained in:
parent
4b9cdf90ca
commit
e912132886
@ -35,12 +35,16 @@
|
|||||||
|
|
||||||
#include <QtGui/QPainter>
|
#include <QtGui/QPainter>
|
||||||
#include <QtGui/QCursor>
|
#include <QtGui/QCursor>
|
||||||
|
#include <QtGui/QGuiApplication>
|
||||||
|
#include <QtGui/QPalette>
|
||||||
|
#include <QtGui/QBitmap>
|
||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
QT_BEGIN_NAMESPACE
|
||||||
|
|
||||||
QShapedPixmapWindow::QShapedPixmapWindow(QScreen *screen)
|
QShapedPixmapWindow::QShapedPixmapWindow(QScreen *screen)
|
||||||
: QWindow(screen),
|
: QWindow(screen),
|
||||||
m_backingStore(0)
|
m_backingStore(0),
|
||||||
|
m_useCompositing(true)
|
||||||
{
|
{
|
||||||
QSurfaceFormat format;
|
QSurfaceFormat format;
|
||||||
format.setAlphaBufferSize(8);
|
format.setAlphaBufferSize(8);
|
||||||
@ -68,7 +72,10 @@ void QShapedPixmapWindow::render()
|
|||||||
|
|
||||||
{
|
{
|
||||||
QPainter p(device);
|
QPainter p(device);
|
||||||
p.setCompositionMode(QPainter::CompositionMode_Source);
|
if (m_useCompositing)
|
||||||
|
p.setCompositionMode(QPainter::CompositionMode_Source);
|
||||||
|
else
|
||||||
|
p.fillRect(rect, QGuiApplication::palette().base());
|
||||||
p.drawPixmap(0, 0, m_pixmap);
|
p.drawPixmap(0, 0, m_pixmap);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,6 +86,8 @@ void QShapedPixmapWindow::render()
|
|||||||
void QShapedPixmapWindow::setPixmap(const QPixmap &pixmap)
|
void QShapedPixmapWindow::setPixmap(const QPixmap &pixmap)
|
||||||
{
|
{
|
||||||
m_pixmap = pixmap;
|
m_pixmap = pixmap;
|
||||||
|
if (!m_useCompositing)
|
||||||
|
setMask(m_pixmap.mask());
|
||||||
}
|
}
|
||||||
|
|
||||||
void QShapedPixmapWindow::setHotspot(const QPoint &hotspot)
|
void QShapedPixmapWindow::setHotspot(const QPoint &hotspot)
|
||||||
|
@ -60,6 +60,7 @@ public:
|
|||||||
|
|
||||||
void render();
|
void render();
|
||||||
|
|
||||||
|
void setUseCompositing(bool on) { m_useCompositing = on; }
|
||||||
void setPixmap(const QPixmap &pixmap);
|
void setPixmap(const QPixmap &pixmap);
|
||||||
void setHotspot(const QPoint &hotspot);
|
void setHotspot(const QPoint &hotspot);
|
||||||
|
|
||||||
@ -72,6 +73,7 @@ private:
|
|||||||
QBackingStore *m_backingStore;
|
QBackingStore *m_backingStore;
|
||||||
QPixmap m_pixmap;
|
QPixmap m_pixmap;
|
||||||
QPoint m_hotSpot;
|
QPoint m_hotSpot;
|
||||||
|
bool m_useCompositing;
|
||||||
};
|
};
|
||||||
|
|
||||||
QT_END_NAMESPACE
|
QT_END_NAMESPACE
|
||||||
|
@ -88,7 +88,7 @@ static QWindow* topLevelAt(const QPoint &pos)
|
|||||||
QBasicDrag::QBasicDrag() :
|
QBasicDrag::QBasicDrag() :
|
||||||
m_restoreCursor(false), m_eventLoop(0),
|
m_restoreCursor(false), m_eventLoop(0),
|
||||||
m_executed_drop_action(Qt::IgnoreAction), m_can_drop(false),
|
m_executed_drop_action(Qt::IgnoreAction), m_can_drop(false),
|
||||||
m_drag(0), m_drag_icon_window(0)
|
m_drag(0), m_drag_icon_window(0), m_useCompositing(true)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -226,6 +226,7 @@ void QBasicDrag::recreateShapedPixmapWindow(QScreen *screen, const QPoint &pos)
|
|||||||
// when QDrag is used without a pixmap - QDrag::setPixmap()
|
// when QDrag is used without a pixmap - QDrag::setPixmap()
|
||||||
m_drag_icon_window = new QShapedPixmapWindow(screen);
|
m_drag_icon_window = new QShapedPixmapWindow(screen);
|
||||||
|
|
||||||
|
m_drag_icon_window->setUseCompositing(m_useCompositing);
|
||||||
m_drag_icon_window->setPixmap(m_drag->pixmap());
|
m_drag_icon_window->setPixmap(m_drag->pixmap());
|
||||||
m_drag_icon_window->setHotspot(m_drag->hotSpot());
|
m_drag_icon_window->setHotspot(m_drag->hotSpot());
|
||||||
m_drag_icon_window->updateGeometry(pos);
|
m_drag_icon_window->updateGeometry(pos);
|
||||||
|
@ -87,6 +87,9 @@ protected:
|
|||||||
bool canDrop() const { return m_can_drop; }
|
bool canDrop() const { return m_can_drop; }
|
||||||
void setCanDrop(bool c) { m_can_drop = c; }
|
void setCanDrop(bool c) { m_can_drop = c; }
|
||||||
|
|
||||||
|
bool useCompositing() const { return m_useCompositing; }
|
||||||
|
void setUseCompositing(bool on) { m_useCompositing = on; }
|
||||||
|
|
||||||
Qt::DropAction executedDropAction() const { return m_executed_drop_action; }
|
Qt::DropAction executedDropAction() const { return m_executed_drop_action; }
|
||||||
void setExecutedDropAction(Qt::DropAction da) { m_executed_drop_action = da; }
|
void setExecutedDropAction(Qt::DropAction da) { m_executed_drop_action = da; }
|
||||||
|
|
||||||
@ -104,6 +107,7 @@ private:
|
|||||||
bool m_can_drop;
|
bool m_can_drop;
|
||||||
QDrag *m_drag;
|
QDrag *m_drag;
|
||||||
QShapedPixmapWindow *m_drag_icon_window;
|
QShapedPixmapWindow *m_drag_icon_window;
|
||||||
|
bool m_useCompositing;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Q_GUI_EXPORT QSimpleDrag : public QBasicDrag
|
class Q_GUI_EXPORT QSimpleDrag : public QBasicDrag
|
||||||
|
@ -275,22 +275,8 @@ QXcbClipboard::QXcbClipboard(QXcbConnection *c)
|
|||||||
m_clientClipboard[QClipboard::Selection] = 0;
|
m_clientClipboard[QClipboard::Selection] = 0;
|
||||||
m_timestamp[QClipboard::Clipboard] = XCB_CURRENT_TIME;
|
m_timestamp[QClipboard::Clipboard] = XCB_CURRENT_TIME;
|
||||||
m_timestamp[QClipboard::Selection] = XCB_CURRENT_TIME;
|
m_timestamp[QClipboard::Selection] = XCB_CURRENT_TIME;
|
||||||
|
m_owner = connection()->getQtSelectionOwner();
|
||||||
|
|
||||||
QXcbScreen *platformScreen = screen();
|
|
||||||
|
|
||||||
int x = 0, y = 0, w = 3, h = 3;
|
|
||||||
|
|
||||||
m_owner = xcb_generate_id(xcb_connection());
|
|
||||||
Q_XCB_CALL(xcb_create_window(xcb_connection(),
|
|
||||||
XCB_COPY_FROM_PARENT, // depth -- same as root
|
|
||||||
m_owner, // window id
|
|
||||||
platformScreen->screen()->root, // parent window id
|
|
||||||
x, y, w, h,
|
|
||||||
0, // border width
|
|
||||||
XCB_WINDOW_CLASS_INPUT_OUTPUT, // window class
|
|
||||||
platformScreen->screen()->root_visual, // visual
|
|
||||||
0, // value mask
|
|
||||||
0)); // value list
|
|
||||||
#ifndef QT_NO_DEBUG
|
#ifndef QT_NO_DEBUG
|
||||||
QByteArray ba("Qt clipboard window");
|
QByteArray ba("Qt clipboard window");
|
||||||
Q_XCB_CALL(xcb_change_property(xcb_connection(),
|
Q_XCB_CALL(xcb_change_property(xcb_connection(),
|
||||||
@ -353,13 +339,7 @@ void QXcbClipboard::incrTransactionPeeker(xcb_generic_event_t *ge, bool &accepte
|
|||||||
|
|
||||||
xcb_window_t QXcbClipboard::getSelectionOwner(xcb_atom_t atom) const
|
xcb_window_t QXcbClipboard::getSelectionOwner(xcb_atom_t atom) const
|
||||||
{
|
{
|
||||||
xcb_connection_t *c = xcb_connection();
|
return connection()->getSelectionOwner(atom);
|
||||||
xcb_get_selection_owner_cookie_t cookie = xcb_get_selection_owner(c, atom);
|
|
||||||
xcb_get_selection_owner_reply_t *reply;
|
|
||||||
reply = xcb_get_selection_owner_reply(c, cookie, 0);
|
|
||||||
xcb_window_t win = reply->owner;
|
|
||||||
free(reply);
|
|
||||||
return win;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
xcb_atom_t QXcbClipboard::atomForMode(QClipboard::Mode mode) const
|
xcb_atom_t QXcbClipboard::atomForMode(QClipboard::Mode mode) const
|
||||||
|
@ -449,6 +449,9 @@ void QXcbConnection::initializeScreens()
|
|||||||
++xcbScreenNumber;
|
++xcbScreenNumber;
|
||||||
} // for each xcb screen
|
} // for each xcb screen
|
||||||
|
|
||||||
|
foreach (QXcbVirtualDesktop *virtualDesktop, m_virtualDesktops)
|
||||||
|
virtualDesktop->subscribeToXFixesSelectionNotify();
|
||||||
|
|
||||||
// If there's no randr extension, or there was some error above, or we found a
|
// If there's no randr extension, or there was some error above, or we found a
|
||||||
// screen which doesn't have outputs for some other reason (e.g. on VNC or ssh -X),
|
// screen which doesn't have outputs for some other reason (e.g. on VNC or ssh -X),
|
||||||
// but the dimensions are known anyway, and we don't already have any lingering
|
// but the dimensions are known anyway, and we don't already have any lingering
|
||||||
@ -507,6 +510,7 @@ QXcbConnection::QXcbConnection(QXcbNativeInterface *nativeInterface, bool canGra
|
|||||||
, m_systemTrayTracker(0)
|
, m_systemTrayTracker(0)
|
||||||
, m_glIntegration(Q_NULLPTR)
|
, m_glIntegration(Q_NULLPTR)
|
||||||
, m_xiGrab(false)
|
, m_xiGrab(false)
|
||||||
|
, m_qtSelectionOwner(0)
|
||||||
{
|
{
|
||||||
#ifdef XCB_USE_XLIB
|
#ifdef XCB_USE_XLIB
|
||||||
Display *dpy = XOpenDisplay(m_displayName.constData());
|
Display *dpy = XOpenDisplay(m_displayName.constData());
|
||||||
@ -551,12 +555,12 @@ QXcbConnection::QXcbConnection(QXcbNativeInterface *nativeInterface, bool canGra
|
|||||||
m_netWmUserTime = XCB_CURRENT_TIME;
|
m_netWmUserTime = XCB_CURRENT_TIME;
|
||||||
|
|
||||||
initializeXRandr();
|
initializeXRandr();
|
||||||
|
initializeXFixes();
|
||||||
initializeScreens();
|
initializeScreens();
|
||||||
|
|
||||||
if (m_screens.isEmpty())
|
if (m_screens.isEmpty())
|
||||||
qFatal("QXcbConnection: no screens available");
|
qFatal("QXcbConnection: no screens available");
|
||||||
|
|
||||||
initializeXFixes();
|
|
||||||
initializeXRender();
|
initializeXRender();
|
||||||
m_xi2Enabled = false;
|
m_xi2Enabled = false;
|
||||||
#if defined(XCB_USE_XINPUT2)
|
#if defined(XCB_USE_XINPUT2)
|
||||||
@ -1139,10 +1143,14 @@ void QXcbConnection::handleXcbEvent(xcb_generic_event_t *event)
|
|||||||
|
|
||||||
if (!handled) {
|
if (!handled) {
|
||||||
if (response_type == xfixes_first_event + XCB_XFIXES_SELECTION_NOTIFY) {
|
if (response_type == xfixes_first_event + XCB_XFIXES_SELECTION_NOTIFY) {
|
||||||
setTime(((xcb_xfixes_selection_notify_event_t *)event)->timestamp);
|
xcb_xfixes_selection_notify_event_t *notify_event = (xcb_xfixes_selection_notify_event_t *)event;
|
||||||
|
setTime(notify_event->timestamp);
|
||||||
#ifndef QT_NO_CLIPBOARD
|
#ifndef QT_NO_CLIPBOARD
|
||||||
m_clipboard->handleXFixesSelectionRequest((xcb_xfixes_selection_notify_event_t *)event);
|
m_clipboard->handleXFixesSelectionRequest(notify_event);
|
||||||
#endif
|
#endif
|
||||||
|
foreach (QXcbVirtualDesktop *virtualDesktop, m_virtualDesktops)
|
||||||
|
virtualDesktop->handleXFixesSelectionNotify(notify_event);
|
||||||
|
|
||||||
handled = true;
|
handled = true;
|
||||||
} else if (has_randr_extension && response_type == xrandr_first_event + XCB_RANDR_NOTIFY) {
|
} else if (has_randr_extension && response_type == xrandr_first_event + XCB_RANDR_NOTIFY) {
|
||||||
updateScreens((xcb_randr_notify_event_t *)event);
|
updateScreens((xcb_randr_notify_event_t *)event);
|
||||||
@ -1371,6 +1379,37 @@ xcb_timestamp_t QXcbConnection::getTimestamp()
|
|||||||
return timestamp;
|
return timestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
xcb_window_t QXcbConnection::getSelectionOwner(xcb_atom_t atom) const
|
||||||
|
{
|
||||||
|
xcb_connection_t *c = xcb_connection();
|
||||||
|
xcb_get_selection_owner_cookie_t cookie = xcb_get_selection_owner(c, atom);
|
||||||
|
xcb_get_selection_owner_reply_t *reply;
|
||||||
|
reply = xcb_get_selection_owner_reply(c, cookie, 0);
|
||||||
|
xcb_window_t win = reply->owner;
|
||||||
|
free(reply);
|
||||||
|
return win;
|
||||||
|
}
|
||||||
|
|
||||||
|
xcb_window_t QXcbConnection::getQtSelectionOwner()
|
||||||
|
{
|
||||||
|
if (!m_qtSelectionOwner) {
|
||||||
|
xcb_screen_t *xcbScreen = primaryVirtualDesktop()->screen();
|
||||||
|
int x = 0, y = 0, w = 3, h = 3;
|
||||||
|
m_qtSelectionOwner = xcb_generate_id(xcb_connection());
|
||||||
|
Q_XCB_CALL(xcb_create_window(xcb_connection(),
|
||||||
|
XCB_COPY_FROM_PARENT, // depth -- same as root
|
||||||
|
m_qtSelectionOwner, // window id
|
||||||
|
xcbScreen->root, // parent window id
|
||||||
|
x, y, w, h,
|
||||||
|
0, // border width
|
||||||
|
XCB_WINDOW_CLASS_INPUT_OUTPUT, // window class
|
||||||
|
xcbScreen->root_visual, // visual
|
||||||
|
0, // value mask
|
||||||
|
0)); // value list
|
||||||
|
}
|
||||||
|
return m_qtSelectionOwner;
|
||||||
|
}
|
||||||
|
|
||||||
xcb_window_t QXcbConnection::rootWindow()
|
xcb_window_t QXcbConnection::rootWindow()
|
||||||
{
|
{
|
||||||
QXcbScreen *s = primaryScreen();
|
QXcbScreen *s = primaryScreen();
|
||||||
|
@ -460,6 +460,8 @@ public:
|
|||||||
bool threadedEventHandling() const { return m_reader->isRunning(); }
|
bool threadedEventHandling() const { return m_reader->isRunning(); }
|
||||||
|
|
||||||
xcb_timestamp_t getTimestamp();
|
xcb_timestamp_t getTimestamp();
|
||||||
|
xcb_window_t getSelectionOwner(xcb_atom_t atom) const;
|
||||||
|
xcb_window_t getQtSelectionOwner();
|
||||||
|
|
||||||
void setButton(Qt::MouseButton button, bool down) { if (down) m_buttons |= button; else m_buttons &= ~button; }
|
void setButton(Qt::MouseButton button, bool down) { if (down) m_buttons |= button; else m_buttons &= ~button; }
|
||||||
Qt::MouseButtons buttons() const { return m_buttons; }
|
Qt::MouseButtons buttons() const { return m_buttons; }
|
||||||
@ -650,6 +652,8 @@ private:
|
|||||||
QXcbGlIntegration *m_glIntegration;
|
QXcbGlIntegration *m_glIntegration;
|
||||||
bool m_xiGrab;
|
bool m_xiGrab;
|
||||||
|
|
||||||
|
xcb_window_t m_qtSelectionOwner;
|
||||||
|
|
||||||
friend class QXcbEventReader;
|
friend class QXcbEventReader;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -191,6 +191,8 @@ void QXcbDrag::startDrag()
|
|||||||
xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, connection()->clipboard()->owner(),
|
xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, connection()->clipboard()->owner(),
|
||||||
atom(QXcbAtom::XdndTypelist),
|
atom(QXcbAtom::XdndTypelist),
|
||||||
XCB_ATOM_ATOM, 32, drag_types.size(), (const void *)drag_types.constData());
|
XCB_ATOM_ATOM, 32, drag_types.size(), (const void *)drag_types.constData());
|
||||||
|
|
||||||
|
setUseCompositing(current_virtual_desktop->compositingActive());
|
||||||
QBasicDrag::startDrag();
|
QBasicDrag::startDrag();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -316,6 +318,7 @@ void QXcbDrag::move(const QPoint &globalPos)
|
|||||||
QPoint deviceIndependentPos = QHighDpiScaling::mapPositionFromNative(globalPos, screen);
|
QPoint deviceIndependentPos = QHighDpiScaling::mapPositionFromNative(globalPos, screen);
|
||||||
|
|
||||||
if (virtualDesktop != current_virtual_desktop) {
|
if (virtualDesktop != current_virtual_desktop) {
|
||||||
|
setUseCompositing(virtualDesktop->compositingActive());
|
||||||
recreateShapedPixmapWindow(static_cast<QPlatformScreen*>(screen)->screen(), deviceIndependentPos);
|
recreateShapedPixmapWindow(static_cast<QPlatformScreen*>(screen)->screen(), deviceIndependentPos);
|
||||||
current_virtual_desktop = virtualDesktop;
|
current_virtual_desktop = virtualDesktop;
|
||||||
} else {
|
} else {
|
||||||
|
@ -54,6 +54,10 @@ QXcbVirtualDesktop::QXcbVirtualDesktop(QXcbConnection *connection, xcb_screen_t
|
|||||||
, m_number(number)
|
, m_number(number)
|
||||||
, m_xSettings(Q_NULLPTR)
|
, m_xSettings(Q_NULLPTR)
|
||||||
{
|
{
|
||||||
|
QByteArray cmAtomName("_NET_WM_CM_S");
|
||||||
|
cmAtomName += QByteArray::number(m_number);
|
||||||
|
m_net_wm_cm_atom = connection->internAtom(cmAtomName.constData());
|
||||||
|
m_compositingActive = connection->getSelectionOwner(m_net_wm_cm_atom);
|
||||||
}
|
}
|
||||||
|
|
||||||
QXcbVirtualDesktop::~QXcbVirtualDesktop()
|
QXcbVirtualDesktop::~QXcbVirtualDesktop()
|
||||||
@ -79,6 +83,30 @@ QXcbXSettings *QXcbVirtualDesktop::xSettings() const
|
|||||||
return m_xSettings;
|
return m_xSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool QXcbVirtualDesktop::compositingActive() const
|
||||||
|
{
|
||||||
|
if (connection()->hasXFixes())
|
||||||
|
return m_compositingActive;
|
||||||
|
else
|
||||||
|
return connection()->getSelectionOwner(m_net_wm_cm_atom);
|
||||||
|
}
|
||||||
|
|
||||||
|
void QXcbVirtualDesktop::handleXFixesSelectionNotify(xcb_xfixes_selection_notify_event_t *notify_event)
|
||||||
|
{
|
||||||
|
if (notify_event->selection == m_net_wm_cm_atom)
|
||||||
|
m_compositingActive = notify_event->owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QXcbVirtualDesktop::subscribeToXFixesSelectionNotify()
|
||||||
|
{
|
||||||
|
if (connection()->hasXFixes()) {
|
||||||
|
const uint32_t mask = XCB_XFIXES_SELECTION_EVENT_MASK_SET_SELECTION_OWNER |
|
||||||
|
XCB_XFIXES_SELECTION_EVENT_MASK_SELECTION_WINDOW_DESTROY |
|
||||||
|
XCB_XFIXES_SELECTION_EVENT_MASK_SELECTION_CLIENT_CLOSE;
|
||||||
|
Q_XCB_CALL(xcb_xfixes_select_selection_input_checked(xcb_connection(), connection()->getQtSelectionOwner(), m_net_wm_cm_atom, mask));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
QXcbScreen::QXcbScreen(QXcbConnection *connection, QXcbVirtualDesktop *virtualDesktop,
|
QXcbScreen::QXcbScreen(QXcbConnection *connection, QXcbVirtualDesktop *virtualDesktop,
|
||||||
xcb_randr_output_t outputId, xcb_randr_get_output_info_reply_t *output,
|
xcb_randr_output_t outputId, xcb_randr_get_output_info_reply_t *output,
|
||||||
QString outputName)
|
QString outputName)
|
||||||
|
@ -39,6 +39,7 @@
|
|||||||
|
|
||||||
#include <xcb/xcb.h>
|
#include <xcb/xcb.h>
|
||||||
#include <xcb/randr.h>
|
#include <xcb/randr.h>
|
||||||
|
#include <xcb/xfixes.h>
|
||||||
|
|
||||||
#include "qxcbobject.h"
|
#include "qxcbobject.h"
|
||||||
#include "qxcbscreen.h"
|
#include "qxcbscreen.h"
|
||||||
@ -69,11 +70,18 @@ public:
|
|||||||
|
|
||||||
QXcbXSettings *xSettings() const;
|
QXcbXSettings *xSettings() const;
|
||||||
|
|
||||||
|
bool compositingActive() const;
|
||||||
|
|
||||||
|
void handleXFixesSelectionNotify(xcb_xfixes_selection_notify_event_t *notify_event);
|
||||||
|
void subscribeToXFixesSelectionNotify();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
xcb_screen_t *m_screen;
|
xcb_screen_t *m_screen;
|
||||||
int m_number;
|
int m_number;
|
||||||
|
|
||||||
QXcbXSettings *m_xSettings;
|
QXcbXSettings *m_xSettings;
|
||||||
|
xcb_atom_t m_net_wm_cm_atom;
|
||||||
|
bool m_compositingActive;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Q_XCB_EXPORT QXcbScreen : public QXcbObject, public QPlatformScreen
|
class Q_XCB_EXPORT QXcbScreen : public QXcbObject, public QPlatformScreen
|
||||||
|
Loading…
x
Reference in New Issue
Block a user