Commit 6eb1c0f0 authored by Tom Anderson's avatar Tom Anderson Committed by Commit Bot

[XProto] Remove Xlib usage in clipboard code

R=adunaev
CC=sky,msisov,nickdiego

Change-Id: I5fc6f0fd97e646044b7d3d0894f6bf5e2b4e2e37
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2253223
Commit-Queue: Thomas Anderson <thomasanderson@chromium.org>
Reviewed-by: default avatarScott Violet <sky@chromium.org>
Cr-Commit-Position: refs/heads/master@{#781980}
parent 7955598d
This diff is collapsed.
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
#include "ui/events/x/x11_window_event_manager.h" #include "ui/events/x/x11_window_event_manager.h"
#include "ui/gfx/x/x11.h" #include "ui/gfx/x/x11.h"
#include "ui/gfx/x/x11_atom_cache.h" #include "ui/gfx/x/x11_atom_cache.h"
#include "ui/gfx/x/xproto.h"
namespace ui { namespace ui {
...@@ -40,10 +41,12 @@ static_assert(KSelectionOwnerTimerPeriodMs <= kIncrementalTransferTimeoutMs, ...@@ -40,10 +41,12 @@ static_assert(KSelectionOwnerTimerPeriodMs <= kIncrementalTransferTimeoutMs,
// Returns a conservative max size of the data we can pass into // Returns a conservative max size of the data we can pass into
// XChangeProperty(). Copied from GTK. // XChangeProperty(). Copied from GTK.
size_t GetMaxRequestSize(XDisplay* display) { size_t GetMaxRequestSize(x11::Connection* connection) {
long extended_max_size = XExtendedMaxRequestSize(display); long extended_max_size = connection->extended_max_request_length();
long max_size = long max_size =
(extended_max_size ? extended_max_size : XMaxRequestSize(display)) - 100; (extended_max_size ? extended_max_size
: connection->setup().maximum_request_length) -
100;
return std::min(static_cast<long>(0x40000), return std::min(static_cast<long>(0x40000),
std::max(static_cast<long>(0), max_size)); std::max(static_cast<long>(0), max_size));
} }
...@@ -79,13 +82,12 @@ void SetSelectionOwner(x11::Window window, ...@@ -79,13 +82,12 @@ void SetSelectionOwner(x11::Window window,
} // namespace } // namespace
SelectionOwner::SelectionOwner(XDisplay* x_display, SelectionOwner::SelectionOwner(x11::Connection* connection,
x11::Window x_window, x11::Window x_window,
x11::Atom selection_name) x11::Atom selection_name)
: x_display_(x_display), : x_window_(x_window),
x_window_(x_window),
selection_name_(selection_name), selection_name_(selection_name),
max_request_size_(GetMaxRequestSize(x_display)) {} max_request_size_(GetMaxRequestSize(connection)) {}
SelectionOwner::~SelectionOwner() { SelectionOwner::~SelectionOwner() {
// If we are the selection owner, we need to release the selection so we // If we are the selection owner, we need to release the selection so we
...@@ -117,22 +119,20 @@ void SelectionOwner::ClearSelectionOwner() { ...@@ -117,22 +119,20 @@ void SelectionOwner::ClearSelectionOwner() {
} }
void SelectionOwner::OnSelectionRequest(const x11::Event& x11_event) { void SelectionOwner::OnSelectionRequest(const x11::Event& x11_event) {
const XEvent& event = x11_event.xlib_event(); auto& request = *x11_event.As<x11::SelectionRequestEvent>();
auto requestor = static_cast<x11::Window>(event.xselectionrequest.requestor); auto requestor = request.requestor;
x11::Atom requested_target = x11::Atom requested_target = request.target;
static_cast<x11::Atom>(event.xselectionrequest.target); x11::Atom requested_property = request.property;
x11::Atom requested_property =
static_cast<x11::Atom>(event.xselectionrequest.property);
// Incrementally build our selection. By default this is a refusal, and we'll // Incrementally build our selection. By default this is a refusal, and we'll
// override the parts indicating success in the different cases. // override the parts indicating success in the different cases.
XEvent reply; x11::SelectionNotifyEvent reply{
reply.xselection.type = SelectionNotify; .time = request.time,
reply.xselection.requestor = static_cast<uint32_t>(requestor); .requestor = requestor,
reply.xselection.selection = event.xselectionrequest.selection; .selection = request.selection,
reply.xselection.target = static_cast<uint32_t>(requested_target); .target = requested_target,
reply.xselection.property = x11::None; // Indicates failure .property = x11::Atom::None, // Indicates failure
reply.xselection.time = event.xselectionrequest.time; };
if (requested_target == gfx::GetAtom(kMultiple)) { if (requested_target == gfx::GetAtom(kMultiple)) {
// The contents of |requested_property| should be a list of // The contents of |requested_property| should be a list of
...@@ -153,16 +153,15 @@ void SelectionOwner::OnSelectionRequest(const x11::Event& x11_event) { ...@@ -153,16 +153,15 @@ void SelectionOwner::OnSelectionRequest(const x11::Event& x11_event) {
ui::SetArrayProperty(requestor, requested_property, ui::SetArrayProperty(requestor, requested_property,
gfx::GetAtom(kAtomPair), conversion_results); gfx::GetAtom(kAtomPair), conversion_results);
reply.xselection.property = static_cast<uint32_t>(requested_property); reply.property = requested_property;
} }
} else { } else {
if (ProcessTarget(requested_target, requestor, requested_property)) if (ProcessTarget(requested_target, requestor, requested_property))
reply.xselection.property = static_cast<uint32_t>(requested_property); reply.property = requested_property;
} }
// Send off the reply. // Send off the reply.
XSendEvent(x_display_, static_cast<uint32_t>(requestor), x11::False, 0, ui::SendEvent(reply, requestor, x11::EventMask::NoEvent);
&reply);
} }
void SelectionOwner::OnSelectionClear(const x11::Event& event) { void SelectionOwner::OnSelectionClear(const x11::Event& event) {
...@@ -173,7 +172,7 @@ void SelectionOwner::OnSelectionClear(const x11::Event& event) { ...@@ -173,7 +172,7 @@ void SelectionOwner::OnSelectionClear(const x11::Event& event) {
} }
bool SelectionOwner::CanDispatchPropertyEvent(const x11::Event& event) { bool SelectionOwner::CanDispatchPropertyEvent(const x11::Event& event) {
return event.xlib_event().xproperty.state == PropertyDelete && return event.As<x11::PropertyNotifyEvent>()->state == x11::Property::Delete &&
FindIncrementalTransferForEvent(event) != incremental_transfers_.end(); FindIncrementalTransferForEvent(event) != incremental_transfers_.end();
} }
...@@ -296,14 +295,12 @@ void SelectionOwner::CompleteIncrementalTransfer( ...@@ -296,14 +295,12 @@ void SelectionOwner::CompleteIncrementalTransfer(
} }
std::vector<SelectionOwner::IncrementalTransfer>::iterator std::vector<SelectionOwner::IncrementalTransfer>::iterator
SelectionOwner::FindIncrementalTransferForEvent(const x11::Event& x11_event) { SelectionOwner::FindIncrementalTransferForEvent(const x11::Event& event) {
const XEvent& event = x11_event.xlib_event();
for (auto it = incremental_transfers_.begin(); for (auto it = incremental_transfers_.begin();
it != incremental_transfers_.end(); ++it) { it != incremental_transfers_.end(); ++it) {
if (it->window == static_cast<x11::Window>(event.xproperty.window) && const auto* prop = event.As<x11::PropertyNotifyEvent>();
it->property == static_cast<x11::Atom>(event.xproperty.atom)) { if (it->window == prop->window && it->property == prop->atom)
return it; return it;
}
} }
return incremental_transfers_.end(); return incremental_transfers_.end();
} }
......
...@@ -34,7 +34,7 @@ COMPONENT_EXPORT(UI_BASE_X) extern const char kTargets[]; ...@@ -34,7 +34,7 @@ COMPONENT_EXPORT(UI_BASE_X) extern const char kTargets[];
// processes. // processes.
class COMPONENT_EXPORT(UI_BASE_X) SelectionOwner { class COMPONENT_EXPORT(UI_BASE_X) SelectionOwner {
public: public:
SelectionOwner(XDisplay* xdisplay, SelectionOwner(x11::Connection* connection,
x11::Window xwindow, x11::Window xwindow,
x11::Atom selection_name); x11::Atom selection_name);
~SelectionOwner(); ~SelectionOwner();
...@@ -126,7 +126,6 @@ class COMPONENT_EXPORT(UI_BASE_X) SelectionOwner { ...@@ -126,7 +126,6 @@ class COMPONENT_EXPORT(UI_BASE_X) SelectionOwner {
const x11::Event& event); const x11::Event& event);
// Our X11 state. // Our X11 state.
XDisplay* x_display_;
x11::Window x_window_; x11::Window x_window_;
// The X11 selection that this instance communicates on. // The X11 selection that this instance communicates on.
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
#include "ui/events/platform/x11/x11_event_source.h" #include "ui/events/platform/x11/x11_event_source.h"
#include "ui/gfx/x/x11_atom_cache.h" #include "ui/gfx/x/x11_atom_cache.h"
#include "ui/gfx/x/x11_types.h" #include "ui/gfx/x/x11_types.h"
#include "ui/gfx/x/xproto.h"
namespace ui { namespace ui {
...@@ -50,11 +51,9 @@ std::vector<uint8_t> CombineData( ...@@ -50,11 +51,9 @@ std::vector<uint8_t> CombineData(
} // namespace } // namespace
SelectionRequestor::SelectionRequestor(XDisplay* x_display, SelectionRequestor::SelectionRequestor(x11::Window x_window,
x11::Window x_window,
XEventDispatcher* dispatcher) XEventDispatcher* dispatcher)
: x_display_(x_display), : x_window_(x_window),
x_window_(x_window),
x_property_(x11::Atom::None), x_property_(x11::Atom::None),
dispatcher_(dispatcher), dispatcher_(dispatcher),
current_request_index_(0u) { current_request_index_(0u) {
...@@ -120,14 +119,13 @@ SelectionData SelectionRequestor::RequestAndWaitForTypes( ...@@ -120,14 +119,13 @@ SelectionData SelectionRequestor::RequestAndWaitForTypes(
return SelectionData(); return SelectionData();
} }
void SelectionRequestor::OnSelectionNotify(const x11::Event& x11_event) { void SelectionRequestor::OnSelectionNotify(
const XEvent& event = x11_event.xlib_event(); const x11::SelectionNotifyEvent& selection) {
Request* request = GetCurrentRequest(); Request* request = GetCurrentRequest();
x11::Atom event_property = static_cast<x11::Atom>(event.xselection.property); x11::Atom event_property = selection.property;
if (!request || request->completed || if (!request || request->completed ||
request->selection != request->selection != selection.selection ||
static_cast<x11::Atom>(event.xselection.selection) || request->target != selection.target) {
request->target != static_cast<x11::Atom>(event.xselection.target)) {
// ICCCM requires us to delete the property passed into SelectionNotify. // ICCCM requires us to delete the property passed into SelectionNotify.
if (event_property != x11::Atom::None) if (event_property != x11::Atom::None)
ui::DeleteProperty(x_window_, event_property); ui::DeleteProperty(x_window_, event_property);
...@@ -158,11 +156,10 @@ void SelectionRequestor::OnSelectionNotify(const x11::Event& x11_event) { ...@@ -158,11 +156,10 @@ void SelectionRequestor::OnSelectionNotify(const x11::Event& x11_event) {
} }
} }
bool SelectionRequestor::CanDispatchPropertyEvent(const x11::Event& x11_event) { bool SelectionRequestor::CanDispatchPropertyEvent(const x11::Event& event) {
const XEvent& event = x11_event.xlib_event(); const auto* prop = event.As<x11::PropertyNotifyEvent>();
return event.xproperty.window == static_cast<uint32_t>(x_window_) && return prop->window == x_window_ && prop->atom == x_property_ &&
static_cast<x11::Atom>(event.xproperty.atom) == x_property_ && prop->state == x11::Property::NewValue;
event.xproperty.state == PropertyNewValue;
} }
void SelectionRequestor::OnPropertyEvent(const x11::Event& event) { void SelectionRequestor::OnPropertyEvent(const x11::Event& event) {
......
...@@ -33,9 +33,7 @@ class SelectionData; ...@@ -33,9 +33,7 @@ class SelectionData;
// implement per-component fast-paths. // implement per-component fast-paths.
class COMPONENT_EXPORT(UI_BASE) SelectionRequestor { class COMPONENT_EXPORT(UI_BASE) SelectionRequestor {
public: public:
SelectionRequestor(XDisplay* xdisplay, SelectionRequestor(x11::Window xwindow, XEventDispatcher* dispatcher);
x11::Window xwindow,
XEventDispatcher* dispatcher);
~SelectionRequestor(); ~SelectionRequestor();
// Does the work of requesting |target| from |selection|, spinning up the // Does the work of requesting |target| from |selection|, spinning up the
...@@ -61,7 +59,7 @@ class COMPONENT_EXPORT(UI_BASE) SelectionRequestor { ...@@ -61,7 +59,7 @@ class COMPONENT_EXPORT(UI_BASE) SelectionRequestor {
// It is our owner's responsibility to plumb X11 SelectionNotify events on // It is our owner's responsibility to plumb X11 SelectionNotify events on
// |xwindow_| to us. // |xwindow_| to us.
void OnSelectionNotify(const x11::Event& event); void OnSelectionNotify(const x11::SelectionNotifyEvent& event);
// Returns true if SelectionOwner can process the XChangeProperty event, // Returns true if SelectionOwner can process the XChangeProperty event,
// |event|. // |event|.
...@@ -121,7 +119,6 @@ class COMPONENT_EXPORT(UI_BASE) SelectionRequestor { ...@@ -121,7 +119,6 @@ class COMPONENT_EXPORT(UI_BASE) SelectionRequestor {
Request* GetCurrentRequest(); Request* GetCurrentRequest();
// Our X11 state. // Our X11 state.
XDisplay* x_display_;
x11::Window x_window_; x11::Window x_window_;
// The property on |x_window_| set by the selection owner with the value of // The property on |x_window_| set by the selection owner with the value of
......
...@@ -55,7 +55,8 @@ class SelectionRequestorTest : public testing::Test { ...@@ -55,7 +55,8 @@ class SelectionRequestorTest : public testing::Test {
event->property = static_cast<uint32_t>(requestor_->x_property_); event->property = static_cast<uint32_t>(requestor_->x_property_);
event->time = x11::CurrentTime; event->time = x11::CurrentTime;
requestor_->OnSelectionNotify(x11::Event(&ge, x11::Connection::Get())); x11::Event xev(&ge, x11::Connection::Get());
requestor_->OnSelectionNotify(*xev.As<x11::SelectionNotifyEvent>());
} }
protected: protected:
...@@ -75,8 +76,7 @@ class SelectionRequestorTest : public testing::Test { ...@@ -75,8 +76,7 @@ class SelectionRequestorTest : public testing::Test {
event_source_ = PlatformEventSource::CreateDefault(); event_source_ = PlatformEventSource::CreateDefault();
CHECK(PlatformEventSource::GetInstance()); CHECK(PlatformEventSource::GetInstance());
requestor_ = requestor_ = std::make_unique<SelectionRequestor>(x_window_, nullptr);
std::make_unique<SelectionRequestor>(x_display_, x_window_, nullptr);
} }
void TearDown() override { void TearDown() override {
......
...@@ -37,38 +37,23 @@ const char kNetscapeURL[] = "_NETSCAPE_URL"; ...@@ -37,38 +37,23 @@ const char kNetscapeURL[] = "_NETSCAPE_URL";
XOSExchangeDataProvider::XOSExchangeDataProvider( XOSExchangeDataProvider::XOSExchangeDataProvider(
x11::Window x_window, x11::Window x_window,
const SelectionFormatMap& selection) const SelectionFormatMap& selection)
: x_display_(gfx::GetXDisplay()), : connection_(x11::Connection::Get()),
x_root_window_(ui::GetX11RootWindow()), x_root_window_(ui::GetX11RootWindow()),
own_window_(false), own_window_(false),
x_window_(x_window), x_window_(x_window),
format_map_(selection), format_map_(selection),
selection_owner_(x_display_, x_window_, gfx::GetAtom(kDndSelection)) {} selection_owner_(connection_, x_window_, gfx::GetAtom(kDndSelection)) {}
XOSExchangeDataProvider::XOSExchangeDataProvider() XOSExchangeDataProvider::XOSExchangeDataProvider()
: x_display_(gfx::GetXDisplay()), : connection_(x11::Connection::Get()),
x_root_window_(ui::GetX11RootWindow()), x_root_window_(ui::GetX11RootWindow()),
own_window_(true), own_window_(true),
x_window_(static_cast<x11::Window>(XCreateWindow( x_window_(CreateDummyWindow("Chromium Drag & Drop Window")),
x_display_, selection_owner_(connection_, x_window_, gfx::GetAtom(kDndSelection)) {}
static_cast<uint32_t>(x_root_window_),
-100, // x
-100, // y
10, // width
10, // height
0, // border width
static_cast<int>(x11::WindowClass::CopyFromParent), // depth
static_cast<int>(x11::WindowClass::InputOnly),
nullptr, // visual
0,
nullptr))),
selection_owner_(x_display_, x_window_, gfx::GetAtom(kDndSelection)) {
XStoreName(x_display_, static_cast<uint32_t>(x_window_),
"Chromium Drag & Drop Window");
}
XOSExchangeDataProvider::~XOSExchangeDataProvider() { XOSExchangeDataProvider::~XOSExchangeDataProvider() {
if (own_window_) if (own_window_)
XDestroyWindow(x_display_, static_cast<uint32_t>(x_window_)); connection_->DestroyWindow({x_window_});
} }
void XOSExchangeDataProvider::TakeOwnershipOfSelection() const { void XOSExchangeDataProvider::TakeOwnershipOfSelection() const {
......
...@@ -125,7 +125,7 @@ class COMPONENT_EXPORT(UI_BASE_X) XOSExchangeDataProvider ...@@ -125,7 +125,7 @@ class COMPONENT_EXPORT(UI_BASE_X) XOSExchangeDataProvider
gfx::Vector2d drag_image_offset_; gfx::Vector2d drag_image_offset_;
// Our X11 state. // Our X11 state.
Display* x_display_; x11::Connection* connection_;
x11::Window x_root_window_; x11::Window x_root_window_;
// In X11, because the IPC parts of drag operations are implemented by // In X11, because the IPC parts of drag operations are implemented by
......
...@@ -455,6 +455,24 @@ void DefineCursor(x11::Window window, x11::Cursor cursor) { ...@@ -455,6 +455,24 @@ void DefineCursor(x11::Window window, x11::Cursor cursor) {
.Sync(); .Sync();
} }
x11::Window CreateDummyWindow(const std::string& name) {
auto* connection = x11::Connection::Get();
auto window = connection->GenerateId<x11::Window>();
connection->CreateWindow({
.wid = window,
.parent = connection->default_root(),
.x = -100,
.y = -100,
.width = 10,
.height = 10,
.c_class = x11::WindowClass::InputOnly,
.override_redirect = x11::Bool32(true),
});
if (!name.empty())
SetStringProperty(window, x11::Atom::WM_NAME, x11::Atom::STRING, name);
return window;
}
bool IsXInput2Available() { bool IsXInput2Available() {
return DeviceDataManagerX11::GetInstance()->IsXInput2Available(); return DeviceDataManagerX11::GetInstance()->IsXInput2Available();
} }
......
...@@ -184,6 +184,18 @@ void SetProperty(x11::Window window, ...@@ -184,6 +184,18 @@ void SetProperty(x11::Window window,
SetArrayProperty(window, name, type, std::vector<T>{value}); SetArrayProperty(window, name, type, std::vector<T>{value});
} }
template <typename T>
void SendEvent(const T& event, x11::Window target, x11::EventMask mask) {
static_assert(T::type_id > 0, "T must be an x11::*Event type");
auto event_bytes = x11::Write(event);
DCHECK_LE(event_bytes.size(), 32ul);
event_bytes.resize(32);
x11::SendEventRequest send_event{false, target, mask};
std::copy(event_bytes.begin(), event_bytes.end(), send_event.event.begin());
x11::Connection::Get()->SendEvent(send_event);
}
COMPONENT_EXPORT(UI_BASE_X) COMPONENT_EXPORT(UI_BASE_X)
void DeleteProperty(x11::Window window, x11::Atom name); void DeleteProperty(x11::Window window, x11::Atom name);
...@@ -211,6 +223,9 @@ void LowerWindow(x11::Window window); ...@@ -211,6 +223,9 @@ void LowerWindow(x11::Window window);
COMPONENT_EXPORT(UI_BASE_X) COMPONENT_EXPORT(UI_BASE_X)
void DefineCursor(x11::Window window, x11::Cursor cursor); void DefineCursor(x11::Window window, x11::Cursor cursor);
COMPONENT_EXPORT(UI_BASE_X)
x11::Window CreateDummyWindow(const std::string& name = "");
// These functions cache their results --------------------------------- // These functions cache their results ---------------------------------
// Returns true if the system supports XINPUT2. // Returns true if the system supports XINPUT2.
......
...@@ -233,6 +233,7 @@ READ_SPECIAL = set([ ...@@ -233,6 +233,7 @@ READ_SPECIAL = set([
WRITE_SPECIAL = set([ WRITE_SPECIAL = set([
('xcb', 'ClientMessage'), ('xcb', 'ClientMessage'),
('xcb', 'UnmapNotify'), ('xcb', 'UnmapNotify'),
('xcb', 'SelectionNotify'),
]) ])
......
...@@ -14,6 +14,8 @@ ...@@ -14,6 +14,8 @@
#include "ui/gfx/x/event.h" #include "ui/gfx/x/event.h"
#include "ui/gfx/x/x11.h" #include "ui/gfx/x/x11.h"
#include "ui/gfx/x/x11_types.h" #include "ui/gfx/x/x11_types.h"
#include "ui/gfx/x/xfixes.h"
#include "ui/gfx/x/xproto.h"
#include "ui/ozone/public/platform_clipboard.h" #include "ui/ozone/public/platform_clipboard.h"
namespace ui { namespace ui {
...@@ -54,9 +56,10 @@ class X11ClipboardOzone : public PlatformClipboard, public XEventDispatcher { ...@@ -54,9 +56,10 @@ class X11ClipboardOzone : public PlatformClipboard, public XEventDispatcher {
// XEventDispatcher: // XEventDispatcher:
bool DispatchXEvent(x11::Event* xev) override; bool DispatchXEvent(x11::Event* xev) override;
bool OnSelectionRequest(const XSelectionRequestEvent& event); bool OnSelectionRequest(const x11::SelectionRequestEvent& event);
bool OnSelectionNotify(const XSelectionEvent& event); bool OnSelectionNotify(const x11::SelectionNotifyEvent& event);
bool OnSetSelectionOwnerNotify(x11::Event* xev); bool OnSetSelectionOwnerNotify(
const x11::XFixes::SelectionNotifyEvent& event);
// Returns an X atom for a clipboard buffer type. // Returns an X atom for a clipboard buffer type.
x11::Atom SelectionAtomForBuffer(ClipboardBuffer buffer) const; x11::Atom SelectionAtomForBuffer(ClipboardBuffer buffer) const;
...@@ -89,7 +92,7 @@ class X11ClipboardOzone : public PlatformClipboard, public XEventDispatcher { ...@@ -89,7 +92,7 @@ class X11ClipboardOzone : public PlatformClipboard, public XEventDispatcher {
const x11::Atom x_property_; const x11::Atom x_property_;
// Our X11 state. // Our X11 state.
Display* const x_display_; x11::Connection* connection_;
// Input-only window used as a selection owner. // Input-only window used as a selection owner.
const x11::Window x_window_; const x11::Window x_window_;
...@@ -99,9 +102,6 @@ class X11ClipboardOzone : public PlatformClipboard, public XEventDispatcher { ...@@ -99,9 +102,6 @@ class X11ClipboardOzone : public PlatformClipboard, public XEventDispatcher {
// TODO(joelhockey): Make clipboard work without xfixes. // TODO(joelhockey): Make clipboard work without xfixes.
bool using_xfixes_ = false; bool using_xfixes_ = false;
// The event base returned by XFixesQueryExtension().
int xfixes_event_base_;
// Notifies whenever clipboard sequence number is changed. // Notifies whenever clipboard sequence number is changed.
PlatformClipboard::SequenceNumberUpdateCb update_sequence_cb_; PlatformClipboard::SequenceNumberUpdateCb update_sequence_cb_;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment