mirror of
https://github.com/genodelabs/genode.git
synced 2025-05-28 21:24:26 +00:00
parent
b22f3c67f0
commit
a801976727
@ -133,21 +133,21 @@ void GenodeConsole::update_video_mode()
|
|||||||
d->SetVideoModeHint(0 /*=display*/,
|
d->SetVideoModeHint(0 /*=display*/,
|
||||||
true /*=enabled*/, false /*=changeOrigin*/,
|
true /*=enabled*/, false /*=changeOrigin*/,
|
||||||
0 /*=originX*/, 0 /*=originY*/,
|
0 /*=originX*/, 0 /*=originY*/,
|
||||||
fb->w(), fb->h(), fb->depth());
|
fb->w(), fb->h(),
|
||||||
|
/* Windows 8 only accepts 32-bpp modes */
|
||||||
|
32);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GenodeConsole::eventWait(IKeyboard * gKeyboard, IMouse * gMouse)
|
void GenodeConsole::handle_input(unsigned)
|
||||||
{
|
{
|
||||||
static LONG64 mt_events [64];
|
static LONG64 mt_events [64];
|
||||||
unsigned mt_number = 0;
|
unsigned mt_number = 0;
|
||||||
|
|
||||||
_receiver.wait_for_signal();
|
|
||||||
|
|
||||||
/* read out input capabilities of guest */
|
/* read out input capabilities of guest */
|
||||||
bool guest_abs = false, guest_rel = false, guest_multi = false;
|
bool guest_abs = false, guest_rel = false, guest_multi = false;
|
||||||
gMouse->COMGETTER(AbsoluteSupported)(&guest_abs);
|
_vbox_mouse->COMGETTER(AbsoluteSupported)(&guest_abs);
|
||||||
gMouse->COMGETTER(RelativeSupported)(&guest_rel);
|
_vbox_mouse->COMGETTER(RelativeSupported)(&guest_rel);
|
||||||
gMouse->COMGETTER(MultiTouchSupported)(&guest_multi);
|
_vbox_mouse->COMGETTER(MultiTouchSupported)(&guest_multi);
|
||||||
|
|
||||||
for (int i = 0, num_ev = _input.flush(); i < num_ev; ++i) {
|
for (int i = 0, num_ev = _input.flush(); i < num_ev; ++i) {
|
||||||
Input::Event &ev = _ev_buf[i];
|
Input::Event &ev = _ev_buf[i];
|
||||||
@ -166,11 +166,11 @@ void GenodeConsole::eventWait(IKeyboard * gKeyboard, IMouse * gMouse)
|
|||||||
(ev.type() == Input::Event::RELEASE) ? 0x80 : 0;
|
(ev.type() == Input::Event::RELEASE) ? 0x80 : 0;
|
||||||
|
|
||||||
if (scan_code.is_normal())
|
if (scan_code.is_normal())
|
||||||
gKeyboard->PutScancode(scan_code.code() | release_bit);
|
_vbox_keyboard->PutScancode(scan_code.code() | release_bit);
|
||||||
|
|
||||||
if (scan_code.is_ext()) {
|
if (scan_code.is_ext()) {
|
||||||
gKeyboard->PutScancode(0xe0);
|
_vbox_keyboard->PutScancode(0xe0);
|
||||||
gKeyboard->PutScancode(scan_code.ext() | release_bit);
|
_vbox_keyboard->PutScancode(scan_code.ext() | release_bit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -204,10 +204,10 @@ void GenodeConsole::eventWait(IKeyboard * gKeyboard, IMouse * gMouse)
|
|||||||
int ry = ev.ay() - _ay;
|
int ry = ev.ay() - _ay;
|
||||||
rx = Genode::min(boundary, Genode::max(-boundary, rx));
|
rx = Genode::min(boundary, Genode::max(-boundary, rx));
|
||||||
ry = Genode::min(boundary, Genode::max(-boundary, ry));
|
ry = Genode::min(boundary, Genode::max(-boundary, ry));
|
||||||
gMouse->PutMouseEvent(rx, ry, 0, 0, buttons);
|
_vbox_mouse->PutMouseEvent(rx, ry, 0, 0, buttons);
|
||||||
} else
|
} else
|
||||||
gMouse->PutMouseEventAbsolute(ev.ax(), ev.ay(), 0,
|
_vbox_mouse->PutMouseEventAbsolute(ev.ax(), ev.ay(), 0,
|
||||||
0, buttons);
|
0, buttons);
|
||||||
|
|
||||||
_ax = ev.ax();
|
_ax = ev.ax();
|
||||||
_ay = ev.ay();
|
_ay = ev.ay();
|
||||||
@ -218,11 +218,11 @@ void GenodeConsole::eventWait(IKeyboard * gKeyboard, IMouse * gMouse)
|
|||||||
|
|
||||||
/* prefer relative motion event */
|
/* prefer relative motion event */
|
||||||
if (guest_rel)
|
if (guest_rel)
|
||||||
gMouse->PutMouseEvent(ev.rx(), ev.ry(), 0, 0, buttons);
|
_vbox_mouse->PutMouseEvent(ev.rx(), ev.ry(), 0, 0, buttons);
|
||||||
else if (guest_abs) {
|
else if (guest_abs) {
|
||||||
_ax += ev.rx();
|
_ax += ev.rx();
|
||||||
_ay += ev.ry();
|
_ay += ev.ry();
|
||||||
gMouse->PutMouseEventAbsolute(_ax, _ay, 0, 0, buttons);
|
_vbox_mouse->PutMouseEventAbsolute(_ax, _ay, 0, 0, buttons);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* only the buttons changed */
|
/* only the buttons changed */
|
||||||
@ -231,28 +231,28 @@ void GenodeConsole::eventWait(IKeyboard * gKeyboard, IMouse * gMouse)
|
|||||||
if (_last_received_motion_event_was_absolute) {
|
if (_last_received_motion_event_was_absolute) {
|
||||||
/* prefer absolute button event */
|
/* prefer absolute button event */
|
||||||
if (guest_abs)
|
if (guest_abs)
|
||||||
gMouse->PutMouseEventAbsolute(_ax, _ay, 0, 0, buttons);
|
_vbox_mouse->PutMouseEventAbsolute(_ax, _ay, 0, 0, buttons);
|
||||||
else if (guest_rel)
|
else if (guest_rel)
|
||||||
gMouse->PutMouseEvent(0, 0, 0, 0, buttons);
|
_vbox_mouse->PutMouseEvent(0, 0, 0, 0, buttons);
|
||||||
} else {
|
} else {
|
||||||
/* prefer relative button event */
|
/* prefer relative button event */
|
||||||
if (guest_rel)
|
if (guest_rel)
|
||||||
gMouse->PutMouseEvent(0, 0, 0, 0, buttons);
|
_vbox_mouse->PutMouseEvent(0, 0, 0, 0, buttons);
|
||||||
else if (guest_abs)
|
else if (guest_abs)
|
||||||
gMouse->PutMouseEventAbsolute(_ax, _ay, 0, 0, buttons);
|
_vbox_mouse->PutMouseEventAbsolute(_ax, _ay, 0, 0, buttons);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_wheel)
|
if (is_wheel)
|
||||||
gMouse->PutMouseEvent(0, 0, ev.rx(), ev.ry(), 0);
|
_vbox_mouse->PutMouseEvent(0, 0, ev.rx(), ev.ry(), 0);
|
||||||
|
|
||||||
if (is_touch) {
|
if (is_touch) {
|
||||||
/* if multitouch queue is full - send it */
|
/* if multitouch queue is full - send it */
|
||||||
if (mt_number >= sizeof(mt_events) / sizeof(mt_events[0])) {
|
if (mt_number >= sizeof(mt_events) / sizeof(mt_events[0])) {
|
||||||
gMouse->PutEventMultiTouch(mt_number, mt_number,
|
_vbox_mouse->PutEventMultiTouch(mt_number, mt_number,
|
||||||
mt_events, RTTimeMilliTS());
|
mt_events, RTTimeMilliTS());
|
||||||
mt_number = 0;
|
mt_number = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -282,8 +282,39 @@ void GenodeConsole::eventWait(IKeyboard * gKeyboard, IMouse * gMouse)
|
|||||||
|
|
||||||
/* if there are elements - send it */
|
/* if there are elements - send it */
|
||||||
if (mt_number)
|
if (mt_number)
|
||||||
gMouse->PutEventMultiTouch(mt_number, mt_number, mt_events,
|
_vbox_mouse->PutEventMultiTouch(mt_number, mt_number, mt_events,
|
||||||
RTTimeMilliTS());
|
RTTimeMilliTS());
|
||||||
|
}
|
||||||
|
|
||||||
|
void GenodeConsole::handle_mode_change(unsigned)
|
||||||
|
{
|
||||||
|
Display *d = getDisplay();
|
||||||
|
Genodefb *fb = dynamic_cast<Genodefb *>(d->getFramebuffer());
|
||||||
|
|
||||||
|
fb->update_mode();
|
||||||
|
update_video_mode();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GenodeConsole::event_loop(IKeyboard * gKeyboard, IMouse * gMouse)
|
||||||
|
{
|
||||||
|
_vbox_keyboard = gKeyboard;
|
||||||
|
_vbox_mouse = gMouse;
|
||||||
|
|
||||||
|
/* register the mode change signal dispatcher at the framebuffer */
|
||||||
|
Display *d = getDisplay();
|
||||||
|
Genodefb *fb = dynamic_cast<Genodefb *>(d->getFramebuffer());
|
||||||
|
fb->mode_sigh(_mode_change_signal_dispatcher);
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
|
||||||
|
Genode::Signal sig = _receiver.wait_for_signal();
|
||||||
|
Genode::Signal_dispatcher_base *dispatcher =
|
||||||
|
dynamic_cast<Genode::Signal_dispatcher_base *>(sig.context());
|
||||||
|
|
||||||
|
if (dispatcher)
|
||||||
|
dispatcher->dispatch(sig.num());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GenodeConsole::onMouseCapabilityChange(BOOL supportsAbsolute, BOOL supportsRelative,
|
void GenodeConsole::onMouseCapabilityChange(BOOL supportsAbsolute, BOOL supportsRelative,
|
||||||
|
@ -107,15 +107,18 @@ class GenodeConsole : public Console {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
Input::Connection _input;
|
Input::Connection _input;
|
||||||
Genode::Signal_receiver _receiver;
|
Genode::Signal_receiver _receiver;
|
||||||
Genode::Signal_context _context;
|
Input::Event *_ev_buf;
|
||||||
Input::Event *_ev_buf;
|
unsigned _ax, _ay;
|
||||||
unsigned _ax, _ay;
|
bool _last_received_motion_event_was_absolute;
|
||||||
bool _last_received_motion_event_was_absolute;
|
Report::Connection _shape_report_connection;
|
||||||
Report::Connection _shape_report_connection;
|
Genode::Attached_dataspace _shape_report_ds;
|
||||||
Genode::Attached_dataspace _shape_report_ds;
|
Vbox_pointer::Shape_report *_shape_report;
|
||||||
Vbox_pointer::Shape_report *_shape_report;
|
IKeyboard *_vbox_keyboard;
|
||||||
|
IMouse *_vbox_mouse;
|
||||||
|
Genode::Signal_dispatcher<GenodeConsole> _input_signal_dispatcher;
|
||||||
|
Genode::Signal_dispatcher<GenodeConsole> _mode_change_signal_dispatcher;
|
||||||
|
|
||||||
bool _key_status[Input::KEY_MAX + 1];
|
bool _key_status[Input::KEY_MAX + 1];
|
||||||
|
|
||||||
@ -136,15 +139,19 @@ class GenodeConsole : public Console {
|
|||||||
_last_received_motion_event_was_absolute(false),
|
_last_received_motion_event_was_absolute(false),
|
||||||
_shape_report_connection("shape", sizeof(Vbox_pointer::Shape_report)),
|
_shape_report_connection("shape", sizeof(Vbox_pointer::Shape_report)),
|
||||||
_shape_report_ds(_shape_report_connection.dataspace()),
|
_shape_report_ds(_shape_report_connection.dataspace()),
|
||||||
_shape_report(_shape_report_ds.local_addr<Vbox_pointer::Shape_report>())
|
_shape_report(_shape_report_ds.local_addr<Vbox_pointer::Shape_report>()),
|
||||||
|
_vbox_keyboard(0),
|
||||||
|
_vbox_mouse(0),
|
||||||
|
_input_signal_dispatcher(_receiver, *this, &GenodeConsole::handle_input),
|
||||||
|
_mode_change_signal_dispatcher(_receiver, *this, &GenodeConsole::handle_mode_change)
|
||||||
{
|
{
|
||||||
for (unsigned i = 0; i <= Input::KEY_MAX; i++)
|
for (unsigned i = 0; i <= Input::KEY_MAX; i++)
|
||||||
_key_status[i] = 0;
|
_key_status[i] = 0;
|
||||||
|
|
||||||
_input.sigh(_receiver.manage(&_context));
|
_input.sigh(_input_signal_dispatcher);
|
||||||
}
|
}
|
||||||
|
|
||||||
void eventWait(IKeyboard * gKeyboard, IMouse * gMouse);
|
void event_loop(IKeyboard * gKeyboard, IMouse * gMouse);
|
||||||
|
|
||||||
void onMouseCapabilityChange(BOOL supportsAbsolute, BOOL supportsRelative,
|
void onMouseCapabilityChange(BOOL supportsAbsolute, BOOL supportsRelative,
|
||||||
BOOL supportsMT, BOOL needsHostCursor);
|
BOOL supportsMT, BOOL needsHostCursor);
|
||||||
@ -209,4 +216,7 @@ class GenodeConsole : public Console {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void update_video_mode();
|
void update_video_mode();
|
||||||
|
|
||||||
|
void handle_input(unsigned);
|
||||||
|
void handle_mode_change(unsigned);
|
||||||
};
|
};
|
||||||
|
@ -26,46 +26,76 @@ class Genodefb :
|
|||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
|
|
||||||
Fb_Genode::Connection _fb;
|
Fb_Genode::Connection _fb;
|
||||||
Fb_Genode::Mode const _fb_mode;
|
|
||||||
void * _fb_base;
|
|
||||||
RTCRITSECT _fb_lock;
|
|
||||||
|
|
||||||
unsigned long _width;
|
/* The mode matching the currently attached dataspace */
|
||||||
unsigned long _height;
|
Fb_Genode::Mode _fb_mode;
|
||||||
|
|
||||||
|
/* The mode at the time when the mode change signal was received */
|
||||||
|
Fb_Genode::Mode _next_fb_mode;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The mode currently used by the VM. Can be smaller than the
|
||||||
|
* framebuffer mode.
|
||||||
|
*/
|
||||||
|
Fb_Genode::Mode _virtual_fb_mode;
|
||||||
|
|
||||||
|
void *_fb_base;
|
||||||
|
RTCRITSECT _fb_lock;
|
||||||
|
|
||||||
|
void _clear_screen()
|
||||||
|
{
|
||||||
|
size_t const num_pixels = _fb_mode.width() * _virtual_fb_mode.height();
|
||||||
|
memset(_fb_base, 0, num_pixels * _fb_mode.bytes_per_pixel());
|
||||||
|
_fb.refresh(0, 0, _virtual_fb_mode.width(), _virtual_fb_mode.height());
|
||||||
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
Genodefb ()
|
Genodefb ()
|
||||||
:
|
:
|
||||||
_fb_mode(_fb.mode()),
|
_fb_mode(_fb.mode()),
|
||||||
_fb_base(Genode::env()->rm_session()->attach(_fb.dataspace())),
|
_next_fb_mode(_fb_mode),
|
||||||
_width(_fb_mode.width()),
|
_virtual_fb_mode(_fb_mode),
|
||||||
_height(_fb_mode.height())
|
_fb_base(Genode::env()->rm_session()->attach(_fb.dataspace()))
|
||||||
{
|
{
|
||||||
int rc = RTCritSectInit(&_fb_lock);
|
int rc = RTCritSectInit(&_fb_lock);
|
||||||
Assert(rc == VINF_SUCCESS);
|
Assert(rc == VINF_SUCCESS);
|
||||||
}
|
}
|
||||||
|
|
||||||
int w() const { return _fb_mode.width(); }
|
/* Return the next mode of the framebuffer */
|
||||||
int h() const { return _fb_mode.height(); }
|
int w() const { return _next_fb_mode.width(); }
|
||||||
int depth() const { return 16; /* XXX */ }
|
int h() const { return _next_fb_mode.height(); }
|
||||||
|
|
||||||
|
void mode_sigh(Genode::Signal_context_capability sigh)
|
||||||
|
{
|
||||||
|
_fb.mode_sigh(sigh);
|
||||||
|
}
|
||||||
|
|
||||||
|
void update_mode()
|
||||||
|
{
|
||||||
|
Lock();
|
||||||
|
_next_fb_mode = _fb.mode();
|
||||||
|
Unlock();
|
||||||
|
}
|
||||||
|
|
||||||
STDMETHODIMP COMGETTER(Width)(ULONG *width)
|
STDMETHODIMP COMGETTER(Width)(ULONG *width)
|
||||||
{
|
{
|
||||||
if (!width)
|
if (!width)
|
||||||
return E_INVALIDARG;
|
return E_INVALIDARG;
|
||||||
|
|
||||||
*width = (int)_width > _fb_mode.width() ? _fb_mode.width() : _width;
|
*width = _virtual_fb_mode.width();
|
||||||
|
|
||||||
return S_OK;
|
return S_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
STDMETHODIMP COMGETTER(Height)(ULONG *height)
|
STDMETHODIMP COMGETTER(Height)(ULONG *height)
|
||||||
{
|
{
|
||||||
if (!height)
|
if (!height)
|
||||||
return E_INVALIDARG;
|
return E_INVALIDARG;
|
||||||
|
|
||||||
*height = (int)_height > _fb_mode.height() ? _fb_mode.height() : _height;
|
*height = _virtual_fb_mode.height();
|
||||||
|
|
||||||
return S_OK;
|
return S_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,7 +120,8 @@ class Genodefb :
|
|||||||
if (!bits)
|
if (!bits)
|
||||||
return E_INVALIDARG;
|
return E_INVALIDARG;
|
||||||
|
|
||||||
*bits = _fb_mode.bytes_per_pixel() * 8;
|
*bits = _virtual_fb_mode.bytes_per_pixel() * 8;
|
||||||
|
|
||||||
return S_OK;
|
return S_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,29 +142,44 @@ class Genodefb :
|
|||||||
ULONG bytesPerLine, ULONG w, ULONG h,
|
ULONG bytesPerLine, ULONG w, ULONG h,
|
||||||
BOOL *finished)
|
BOOL *finished)
|
||||||
{
|
{
|
||||||
/* clear screen to avoid artefacts during resize */
|
HRESULT result = E_FAIL;
|
||||||
size_t const num_pixels = _fb_mode.width() * _fb_mode.height();
|
|
||||||
memset(_fb_base, 0, num_pixels * _fb_mode.bytes_per_pixel());
|
|
||||||
|
|
||||||
_fb.refresh(0, 0, _fb_mode.width(), _fb_mode.height());
|
Lock();
|
||||||
|
|
||||||
/* bitsPerPixel == 0 is set by DevVGA when in text mode */
|
bool ok = (w <= (ULONG)_next_fb_mode.width()) &&
|
||||||
bool ok = ((bitsPerPixel == 16) || (bitsPerPixel == 0)) &&
|
(h <= (ULONG)_next_fb_mode.height());
|
||||||
(w <= (ULONG)_fb_mode.width()) &&
|
|
||||||
(h <= (ULONG)_fb_mode.height());
|
|
||||||
if (ok)
|
|
||||||
PINF("fb resize : %lux%lu@%zu -> %ux%u@%u", _width, _height,
|
|
||||||
_fb_mode.bytes_per_pixel() * 8, w, h, bitsPerPixel);
|
|
||||||
else
|
|
||||||
PWRN("fb resize : %lux%lu@%zu -> %ux%u@%u ignored", _width, _height,
|
|
||||||
_fb_mode.bytes_per_pixel() * 8, w, h, bitsPerPixel);
|
|
||||||
|
|
||||||
_width = w;
|
if (ok) {
|
||||||
_height = h;
|
PINF("fb resize : %dx%d@%zu -> %ux%u@%u",
|
||||||
|
_virtual_fb_mode.width(), _virtual_fb_mode.height(),
|
||||||
|
_virtual_fb_mode.bytes_per_pixel() * 8, w, h, bitsPerPixel);
|
||||||
|
|
||||||
|
if ((w < (ULONG)_next_fb_mode.width()) ||
|
||||||
|
(h < (ULONG)_next_fb_mode.height())) {
|
||||||
|
/* clear the old content around the new, smaller area. */
|
||||||
|
_clear_screen();
|
||||||
|
}
|
||||||
|
|
||||||
|
_fb_mode = _next_fb_mode;
|
||||||
|
|
||||||
|
_virtual_fb_mode = Fb_Genode::Mode(w, h, Fb_Genode::Mode::RGB565);
|
||||||
|
|
||||||
|
Genode::env()->rm_session()->detach(_fb_base);
|
||||||
|
|
||||||
|
_fb_base = Genode::env()->rm_session()->attach(_fb.dataspace());
|
||||||
|
|
||||||
|
result = S_OK;
|
||||||
|
|
||||||
|
} else
|
||||||
|
PWRN("fb resize : %dx%d@%zu -> %ux%u@%u ignored",
|
||||||
|
_virtual_fb_mode.width(), _virtual_fb_mode.height(),
|
||||||
|
_virtual_fb_mode.bytes_per_pixel() * 8, w, h, bitsPerPixel);
|
||||||
|
|
||||||
*finished = true;
|
*finished = true;
|
||||||
|
|
||||||
return S_OK;
|
Unlock();
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
STDMETHODIMP COMGETTER(PixelFormat) (ULONG *format)
|
STDMETHODIMP COMGETTER(PixelFormat) (ULONG *format)
|
||||||
@ -177,9 +223,8 @@ class Genodefb :
|
|||||||
if (!supported)
|
if (!supported)
|
||||||
return E_POINTER;
|
return E_POINTER;
|
||||||
|
|
||||||
*supported = ((width <= (ULONG)_fb_mode.width()) &&
|
*supported = ((width <= (ULONG)_next_fb_mode.width()) &&
|
||||||
(height <= (ULONG)_fb_mode.height()) &&
|
(height <= (ULONG)_next_fb_mode.height()));
|
||||||
(bpp == _fb_mode.bytes_per_pixel() * 8));
|
|
||||||
|
|
||||||
return S_OK;
|
return S_OK;
|
||||||
}
|
}
|
||||||
|
@ -198,9 +198,8 @@ HRESULT setupmachine()
|
|||||||
/* handle input of Genode and forward it to VMM layer */
|
/* handle input of Genode and forward it to VMM layer */
|
||||||
ComPtr<GenodeConsole> genodeConsole = gConsole;
|
ComPtr<GenodeConsole> genodeConsole = gConsole;
|
||||||
RTLogPrintf("genodeConsole = %p\n", genodeConsole);
|
RTLogPrintf("genodeConsole = %p\n", genodeConsole);
|
||||||
while (true) {
|
|
||||||
genodeConsole->eventWait(gKeyboard, gMouse);
|
genodeConsole->event_loop(gKeyboard, gMouse);
|
||||||
}
|
|
||||||
|
|
||||||
Assert(!"return not expected");
|
Assert(!"return not expected");
|
||||||
return E_FAIL;
|
return E_FAIL;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user