Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
W
wine-cw
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
wine
wine-cw
Commits
591fd06b
Commit
591fd06b
authored
May 15, 2022
by
Zebediah Figura
Committed by
Alexandre Julliard
Jun 17, 2022
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
win32u: Move NtUserGetRawInputBuffer from user32.
parent
db43005c
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
260 additions
and
117 deletions
+260
-117
rawinput.c
dlls/user32/rawinput.c
+0
-111
user32.spec
dlls/user32/user32.spec
+1
-1
user_private.h
dlls/user32/user_private.h
+0
-4
gdiobj.c
dlls/win32u/gdiobj.c
+1
-0
ntuser_private.h
dlls/win32u/ntuser_private.h
+4
-0
rawinput.c
dlls/win32u/rawinput.c
+245
-0
win32u.spec
dlls/win32u/win32u.spec
+1
-1
win32u_private.h
dlls/win32u/win32u_private.h
+1
-0
wrappers.c
dlls/win32u/wrappers.c
+6
-0
ntuser.h
include/ntuser.h
+1
-0
No files found.
dlls/user32/rawinput.c
View file @
591fd06b
...
...
@@ -593,117 +593,6 @@ BOOL WINAPI DECLSPEC_HOTPATCH RegisterRawInputDevices(const RAWINPUTDEVICE *devi
return
ret
;
}
#ifdef _WIN64
typedef
RAWINPUTHEADER
RAWINPUTHEADER64
;
typedef
RAWINPUT
RAWINPUT64
;
#else
typedef
struct
{
DWORD
dwType
;
DWORD
dwSize
;
ULONGLONG
hDevice
;
ULONGLONG
wParam
;
}
RAWINPUTHEADER64
;
typedef
struct
{
RAWINPUTHEADER64
header
;
union
{
RAWMOUSE
mouse
;
RAWKEYBOARD
keyboard
;
RAWHID
hid
;
}
data
;
}
RAWINPUT64
;
#endif
/***********************************************************************
* GetRawInputBuffer (USER32.@)
*/
UINT
WINAPI
DECLSPEC_HOTPATCH
GetRawInputBuffer
(
RAWINPUT
*
data
,
UINT
*
data_size
,
UINT
header_size
)
{
struct
hardware_msg_data
*
msg_data
;
struct
rawinput_thread_data
*
thread_data
;
RAWINPUT
*
rawinput
;
UINT
count
=
0
,
remaining
,
rawinput_size
,
next_size
,
overhead
;
BOOL
is_wow64
;
int
i
;
if
(
IsWow64Process
(
GetCurrentProcess
(),
&
is_wow64
)
&&
is_wow64
)
rawinput_size
=
sizeof
(
RAWINPUT64
);
else
rawinput_size
=
sizeof
(
RAWINPUT
);
overhead
=
rawinput_size
-
sizeof
(
RAWINPUT
);
if
(
header_size
!=
sizeof
(
RAWINPUTHEADER
))
{
WARN
(
"Invalid structure size %u.
\n
"
,
header_size
);
SetLastError
(
ERROR_INVALID_PARAMETER
);
return
~
0U
;
}
if
(
!
data_size
)
{
SetLastError
(
ERROR_INVALID_PARAMETER
);
return
~
0U
;
}
if
(
!
data
)
{
TRACE
(
"data %p, data_size %p (%u), header_size %u
\n
"
,
data
,
data_size
,
*
data_size
,
header_size
);
SERVER_START_REQ
(
get_rawinput_buffer
)
{
req
->
rawinput_size
=
rawinput_size
;
req
->
buffer_size
=
0
;
if
(
wine_server_call
(
req
))
return
~
0U
;
*
data_size
=
reply
->
next_size
;
}
SERVER_END_REQ
;
return
0
;
}
if
(
!
(
thread_data
=
rawinput_thread_data
()))
return
~
0U
;
rawinput
=
thread_data
->
buffer
;
/* first RAWINPUT block in the buffer is used for WM_INPUT message data */
msg_data
=
(
struct
hardware_msg_data
*
)
NEXTRAWINPUTBLOCK
(
rawinput
);
SERVER_START_REQ
(
get_rawinput_buffer
)
{
req
->
rawinput_size
=
rawinput_size
;
req
->
buffer_size
=
*
data_size
;
wine_server_set_reply
(
req
,
msg_data
,
RAWINPUT_BUFFER_SIZE
-
rawinput
->
header
.
dwSize
);
if
(
wine_server_call
(
req
))
return
~
0U
;
next_size
=
reply
->
next_size
;
count
=
reply
->
count
;
}
SERVER_END_REQ
;
remaining
=
*
data_size
;
for
(
i
=
0
;
i
<
count
;
++
i
)
{
data
->
header
.
dwSize
=
remaining
;
if
(
!
rawinput_from_hardware_message
(
data
,
msg_data
))
break
;
if
(
overhead
)
memmove
((
char
*
)
&
data
->
data
+
overhead
,
&
data
->
data
,
data
->
header
.
dwSize
-
sizeof
(
RAWINPUTHEADER
));
data
->
header
.
dwSize
+=
overhead
;
remaining
-=
data
->
header
.
dwSize
;
data
=
NEXTRAWINPUTBLOCK
(
data
);
msg_data
=
(
struct
hardware_msg_data
*
)((
char
*
)
msg_data
+
msg_data
->
size
);
}
if
(
count
==
0
&&
next_size
==
0
)
*
data_size
=
0
;
else
if
(
next_size
==
0
)
next_size
=
rawinput_size
;
if
(
next_size
&&
*
data_size
<=
next_size
)
{
SetLastError
(
ERROR_INSUFFICIENT_BUFFER
);
*
data_size
=
next_size
;
count
=
~
0U
;
}
if
(
count
)
TRACE
(
"data %p, data_size %p (%u), header_size %u, count %u
\n
"
,
data
,
data_size
,
*
data_size
,
header_size
,
count
);
return
count
;
}
/***********************************************************************
* GetRawInputDeviceInfoA (USER32.@)
*/
...
...
dlls/user32/user32.spec
View file @
591fd06b
...
...
@@ -366,7 +366,7 @@
@ stdcall GetPropA(long str)
@ stdcall GetPropW(long wstr)
@ stdcall GetQueueStatus(long) NtUserGetQueueStatus
@ stdcall GetRawInputBuffer(ptr ptr long)
@ stdcall GetRawInputBuffer(ptr ptr long)
NtUserGetRawInputBuffer
@ stdcall GetRawInputData(ptr long ptr ptr long) NtUserGetRawInputData
@ stdcall GetRawInputDeviceInfoA(ptr long ptr ptr)
@ stdcall GetRawInputDeviceInfoW(ptr long ptr ptr)
...
...
dlls/user32/user_private.h
View file @
591fd06b
...
...
@@ -47,10 +47,6 @@ struct wm_char_mapping_data
MSG
get_msg
;
};
/* on windows the buffer capacity is quite large as well, enough to */
/* hold up to 10s of 1kHz mouse rawinput events */
#define RAWINPUT_BUFFER_SIZE (512*1024)
extern
BOOL
(
WINAPI
*
imm_register_window
)(
HWND
)
DECLSPEC_HIDDEN
;
extern
void
(
WINAPI
*
imm_unregister_window
)(
HWND
)
DECLSPEC_HIDDEN
;
...
...
dlls/win32u/gdiobj.c
View file @
591fd06b
...
...
@@ -1184,6 +1184,7 @@ static struct unix_funcs unix_funcs =
NtUserGetMessage
,
NtUserGetPriorityClipboardFormat
,
NtUserGetQueueStatus
,
NtUserGetRawInputBuffer
,
NtUserGetRawInputData
,
NtUserGetSystemMenu
,
NtUserGetUpdateRect
,
...
...
dlls/win32u/ntuser_private.h
View file @
591fd06b
...
...
@@ -68,6 +68,10 @@ struct rawinput_thread_data
RAWINPUT
buffer
[
1
];
/* rawinput message data buffer */
};
/* on windows the buffer capacity is quite large as well, enough to */
/* hold up to 10s of 1kHz mouse rawinput events */
#define RAWINPUT_BUFFER_SIZE (512 * 1024)
struct
user_object
{
HANDLE
handle
;
...
...
dlls/win32u/rawinput.c
View file @
591fd06b
...
...
@@ -23,6 +23,7 @@
#pragma makedep unix
#endif
#include <stdbool.h>
#include "win32u_private.h"
#include "ntuser_private.h"
#include "wine/server.h"
...
...
@@ -30,6 +31,250 @@
WINE_DEFAULT_DEBUG_CHANNEL
(
rawinput
);
#define WINE_MOUSE_HANDLE ((HANDLE)1)
#define WINE_KEYBOARD_HANDLE ((HANDLE)2)
#ifdef _WIN64
typedef
RAWINPUTHEADER
RAWINPUTHEADER64
;
typedef
RAWINPUT
RAWINPUT64
;
#else
typedef
struct
{
DWORD
dwType
;
DWORD
dwSize
;
ULONGLONG
hDevice
;
ULONGLONG
wParam
;
}
RAWINPUTHEADER64
;
typedef
struct
{
RAWINPUTHEADER64
header
;
union
{
RAWMOUSE
mouse
;
RAWKEYBOARD
keyboard
;
RAWHID
hid
;
}
data
;
}
RAWINPUT64
;
#endif
static
bool
rawinput_from_hardware_message
(
RAWINPUT
*
rawinput
,
const
struct
hardware_msg_data
*
msg_data
)
{
SIZE_T
size
;
rawinput
->
header
.
dwType
=
msg_data
->
rawinput
.
type
;
if
(
msg_data
->
rawinput
.
type
==
RIM_TYPEMOUSE
)
{
static
const
unsigned
int
button_flags
[]
=
{
0
,
/* MOUSEEVENTF_MOVE */
RI_MOUSE_LEFT_BUTTON_DOWN
,
/* MOUSEEVENTF_LEFTDOWN */
RI_MOUSE_LEFT_BUTTON_UP
,
/* MOUSEEVENTF_LEFTUP */
RI_MOUSE_RIGHT_BUTTON_DOWN
,
/* MOUSEEVENTF_RIGHTDOWN */
RI_MOUSE_RIGHT_BUTTON_UP
,
/* MOUSEEVENTF_RIGHTUP */
RI_MOUSE_MIDDLE_BUTTON_DOWN
,
/* MOUSEEVENTF_MIDDLEDOWN */
RI_MOUSE_MIDDLE_BUTTON_UP
,
/* MOUSEEVENTF_MIDDLEUP */
};
unsigned
int
i
;
rawinput
->
header
.
dwSize
=
FIELD_OFFSET
(
RAWINPUT
,
data
)
+
sizeof
(
RAWMOUSE
);
rawinput
->
header
.
hDevice
=
WINE_MOUSE_HANDLE
;
rawinput
->
header
.
wParam
=
0
;
rawinput
->
data
.
mouse
.
usFlags
=
MOUSE_MOVE_RELATIVE
;
rawinput
->
data
.
mouse
.
usButtonFlags
=
0
;
rawinput
->
data
.
mouse
.
usButtonData
=
0
;
for
(
i
=
1
;
i
<
ARRAY_SIZE
(
button_flags
);
++
i
)
{
if
(
msg_data
->
flags
&
(
1
<<
i
))
rawinput
->
data
.
mouse
.
usButtonFlags
|=
button_flags
[
i
];
}
if
(
msg_data
->
flags
&
MOUSEEVENTF_WHEEL
)
{
rawinput
->
data
.
mouse
.
usButtonFlags
|=
RI_MOUSE_WHEEL
;
rawinput
->
data
.
mouse
.
usButtonData
=
msg_data
->
rawinput
.
mouse
.
data
;
}
if
(
msg_data
->
flags
&
MOUSEEVENTF_HWHEEL
)
{
rawinput
->
data
.
mouse
.
usButtonFlags
|=
RI_MOUSE_HORIZONTAL_WHEEL
;
rawinput
->
data
.
mouse
.
usButtonData
=
msg_data
->
rawinput
.
mouse
.
data
;
}
if
(
msg_data
->
flags
&
MOUSEEVENTF_XDOWN
)
{
if
(
msg_data
->
rawinput
.
mouse
.
data
==
XBUTTON1
)
rawinput
->
data
.
mouse
.
usButtonFlags
|=
RI_MOUSE_BUTTON_4_DOWN
;
else
if
(
msg_data
->
rawinput
.
mouse
.
data
==
XBUTTON2
)
rawinput
->
data
.
mouse
.
usButtonFlags
|=
RI_MOUSE_BUTTON_5_DOWN
;
}
if
(
msg_data
->
flags
&
MOUSEEVENTF_XUP
)
{
if
(
msg_data
->
rawinput
.
mouse
.
data
==
XBUTTON1
)
rawinput
->
data
.
mouse
.
usButtonFlags
|=
RI_MOUSE_BUTTON_4_UP
;
else
if
(
msg_data
->
rawinput
.
mouse
.
data
==
XBUTTON2
)
rawinput
->
data
.
mouse
.
usButtonFlags
|=
RI_MOUSE_BUTTON_5_UP
;
}
rawinput
->
data
.
mouse
.
ulRawButtons
=
0
;
rawinput
->
data
.
mouse
.
lLastX
=
msg_data
->
rawinput
.
mouse
.
x
;
rawinput
->
data
.
mouse
.
lLastY
=
msg_data
->
rawinput
.
mouse
.
y
;
rawinput
->
data
.
mouse
.
ulExtraInformation
=
msg_data
->
info
;
}
else
if
(
msg_data
->
rawinput
.
type
==
RIM_TYPEKEYBOARD
)
{
rawinput
->
header
.
dwSize
=
FIELD_OFFSET
(
RAWINPUT
,
data
)
+
sizeof
(
RAWKEYBOARD
);
rawinput
->
header
.
hDevice
=
WINE_KEYBOARD_HANDLE
;
rawinput
->
header
.
wParam
=
0
;
rawinput
->
data
.
keyboard
.
MakeCode
=
msg_data
->
rawinput
.
kbd
.
scan
;
rawinput
->
data
.
keyboard
.
Flags
=
(
msg_data
->
flags
&
KEYEVENTF_KEYUP
)
?
RI_KEY_BREAK
:
RI_KEY_MAKE
;
if
(
msg_data
->
flags
&
KEYEVENTF_EXTENDEDKEY
)
rawinput
->
data
.
keyboard
.
Flags
|=
RI_KEY_E0
;
rawinput
->
data
.
keyboard
.
Reserved
=
0
;
switch
(
msg_data
->
rawinput
.
kbd
.
vkey
)
{
case
VK_LSHIFT
:
case
VK_RSHIFT
:
rawinput
->
data
.
keyboard
.
VKey
=
VK_SHIFT
;
rawinput
->
data
.
keyboard
.
Flags
&=
~
RI_KEY_E0
;
break
;
case
VK_LCONTROL
:
case
VK_RCONTROL
:
rawinput
->
data
.
keyboard
.
VKey
=
VK_CONTROL
;
break
;
case
VK_LMENU
:
case
VK_RMENU
:
rawinput
->
data
.
keyboard
.
VKey
=
VK_MENU
;
break
;
default:
rawinput
->
data
.
keyboard
.
VKey
=
msg_data
->
rawinput
.
kbd
.
vkey
;
break
;
}
rawinput
->
data
.
keyboard
.
Message
=
msg_data
->
rawinput
.
kbd
.
message
;
rawinput
->
data
.
keyboard
.
ExtraInformation
=
msg_data
->
info
;
}
else
if
(
msg_data
->
rawinput
.
type
==
RIM_TYPEHID
)
{
size
=
msg_data
->
size
-
sizeof
(
*
msg_data
);
if
(
size
>
rawinput
->
header
.
dwSize
-
sizeof
(
*
rawinput
))
return
false
;
rawinput
->
header
.
dwSize
=
FIELD_OFFSET
(
RAWINPUT
,
data
.
hid
.
bRawData
)
+
size
;
rawinput
->
header
.
hDevice
=
ULongToHandle
(
msg_data
->
rawinput
.
hid
.
device
);
rawinput
->
header
.
wParam
=
0
;
rawinput
->
data
.
hid
.
dwCount
=
msg_data
->
rawinput
.
hid
.
count
;
rawinput
->
data
.
hid
.
dwSizeHid
=
msg_data
->
rawinput
.
hid
.
length
;
memcpy
(
rawinput
->
data
.
hid
.
bRawData
,
msg_data
+
1
,
size
);
}
else
{
FIXME
(
"Unhandled rawinput type %#x.
\n
"
,
msg_data
->
rawinput
.
type
);
return
false
;
}
return
true
;
}
/**********************************************************************
* NtUserGetRawInputBuffer (win32u.@)
*/
UINT
WINAPI
NtUserGetRawInputBuffer
(
RAWINPUT
*
data
,
UINT
*
data_size
,
UINT
header_size
)
{
unsigned
int
count
=
0
,
remaining
,
rawinput_size
,
next_size
,
overhead
;
struct
rawinput_thread_data
*
thread_data
;
struct
hardware_msg_data
*
msg_data
;
RAWINPUT
*
rawinput
;
int
i
;
if
(
NtCurrentTeb
()
->
WowTebOffset
)
rawinput_size
=
sizeof
(
RAWINPUT64
);
else
rawinput_size
=
sizeof
(
RAWINPUT
);
overhead
=
rawinput_size
-
sizeof
(
RAWINPUT
);
if
(
header_size
!=
sizeof
(
RAWINPUTHEADER
))
{
WARN
(
"Invalid structure size %u.
\n
"
,
header_size
);
SetLastError
(
ERROR_INVALID_PARAMETER
);
return
~
0u
;
}
if
(
!
data_size
)
{
SetLastError
(
ERROR_INVALID_PARAMETER
);
return
~
0u
;
}
if
(
!
data
)
{
TRACE
(
"data %p, data_size %p (%u), header_size %u
\n
"
,
data
,
data_size
,
*
data_size
,
header_size
);
SERVER_START_REQ
(
get_rawinput_buffer
)
{
req
->
rawinput_size
=
rawinput_size
;
req
->
buffer_size
=
0
;
if
(
wine_server_call
(
req
))
return
~
0u
;
*
data_size
=
reply
->
next_size
;
}
SERVER_END_REQ
;
return
0
;
}
if
(
!
user_callbacks
||
!
(
thread_data
=
user_callbacks
->
get_rawinput_thread_data
()))
return
~
0u
;
rawinput
=
thread_data
->
buffer
;
/* first RAWINPUT block in the buffer is used for WM_INPUT message data */
msg_data
=
(
struct
hardware_msg_data
*
)
NEXTRAWINPUTBLOCK
(
rawinput
);
SERVER_START_REQ
(
get_rawinput_buffer
)
{
req
->
rawinput_size
=
rawinput_size
;
req
->
buffer_size
=
*
data_size
;
wine_server_set_reply
(
req
,
msg_data
,
RAWINPUT_BUFFER_SIZE
-
rawinput
->
header
.
dwSize
);
if
(
wine_server_call
(
req
))
return
~
0u
;
next_size
=
reply
->
next_size
;
count
=
reply
->
count
;
}
SERVER_END_REQ
;
remaining
=
*
data_size
;
for
(
i
=
0
;
i
<
count
;
++
i
)
{
data
->
header
.
dwSize
=
remaining
;
if
(
!
rawinput_from_hardware_message
(
data
,
msg_data
))
break
;
if
(
overhead
)
{
memmove
(
(
char
*
)
&
data
->
data
+
overhead
,
&
data
->
data
,
data
->
header
.
dwSize
-
sizeof
(
RAWINPUTHEADER
)
);
}
data
->
header
.
dwSize
+=
overhead
;
remaining
-=
data
->
header
.
dwSize
;
data
=
NEXTRAWINPUTBLOCK
(
data
);
msg_data
=
(
struct
hardware_msg_data
*
)((
char
*
)
msg_data
+
msg_data
->
size
);
}
if
(
!
next_size
)
{
if
(
!
count
)
*
data_size
=
0
;
else
next_size
=
rawinput_size
;
}
if
(
next_size
&&
*
data_size
<=
next_size
)
{
SetLastError
(
ERROR_INSUFFICIENT_BUFFER
);
*
data_size
=
next_size
;
count
=
~
0u
;
}
TRACE
(
"data %p, data_size %p (%u), header_size %u, count %u
\n
"
,
data
,
data_size
,
*
data_size
,
header_size
,
count
);
return
count
;
}
/**********************************************************************
* NtUserGetRawInputData (win32u.@)
*/
...
...
dlls/win32u/win32u.spec
View file @
591fd06b
...
...
@@ -983,7 +983,7 @@
@ stdcall -syscall NtUserGetProp(long wstr)
@ stdcall NtUserGetQueueStatus(long)
@ stub NtUserGetQueueStatusReadonly
@ st
ub NtUserGetRawInputBuffer
@ st
dcall NtUserGetRawInputBuffer(ptr ptr long)
@ stdcall NtUserGetRawInputData(ptr long ptr ptr long)
@ stub NtUserGetRawInputDeviceInfo
@ stub NtUserGetRawInputDeviceList
...
...
dlls/win32u/win32u_private.h
View file @
591fd06b
...
...
@@ -247,6 +247,7 @@ struct unix_funcs
BOOL
(
WINAPI
*
pNtUserGetMessage
)(
MSG
*
msg
,
HWND
hwnd
,
UINT
first
,
UINT
last
);
INT
(
WINAPI
*
pNtUserGetPriorityClipboardFormat
)(
UINT
*
list
,
INT
count
);
DWORD
(
WINAPI
*
pNtUserGetQueueStatus
)(
UINT
flags
);
UINT
(
WINAPI
*
pNtUserGetRawInputBuffer
)(
RAWINPUT
*
data
,
UINT
*
data_size
,
UINT
header_size
);
UINT
(
WINAPI
*
pNtUserGetRawInputData
)(
HRAWINPUT
rawinput
,
UINT
command
,
void
*
data
,
UINT
*
data_size
,
UINT
header_size
);
HMENU
(
WINAPI
*
pNtUserGetSystemMenu
)(
HWND
hwnd
,
BOOL
revert
);
...
...
dlls/win32u/wrappers.c
View file @
591fd06b
...
...
@@ -1048,6 +1048,12 @@ DWORD WINAPI NtUserGetQueueStatus( UINT flags )
return
unix_funcs
->
pNtUserGetQueueStatus
(
flags
);
}
UINT
WINAPI
DECLSPEC_HOTPATCH
NtUserGetRawInputBuffer
(
RAWINPUT
*
data
,
UINT
*
data_size
,
UINT
header_size
)
{
if
(
!
unix_funcs
)
return
~
0u
;
return
unix_funcs
->
pNtUserGetRawInputBuffer
(
data
,
data_size
,
header_size
);
}
UINT
WINAPI
NtUserGetRawInputData
(
HRAWINPUT
rawinput
,
UINT
command
,
void
*
data
,
UINT
*
data_size
,
UINT
header_size
)
{
if
(
!
unix_funcs
)
return
~
0u
;
...
...
include/ntuser.h
View file @
591fd06b
...
...
@@ -606,6 +606,7 @@ HWINSTA WINAPI NtUserGetProcessWindowStation(void);
HANDLE
WINAPI
NtUserGetProp
(
HWND
hwnd
,
const
WCHAR
*
str
);
ULONG
WINAPI
NtUserGetProcessDpiAwarenessContext
(
HANDLE
process
);
DWORD
WINAPI
NtUserGetQueueStatus
(
UINT
flags
);
UINT
WINAPI
NtUserGetRawInputBuffer
(
RAWINPUT
*
data
,
UINT
*
data_size
,
UINT
header_size
);
UINT
WINAPI
NtUserGetRawInputData
(
HRAWINPUT
rawinput
,
UINT
command
,
void
*
data
,
UINT
*
data_size
,
UINT
header_size
);
ULONG
WINAPI
NtUserGetSystemDpiForProcess
(
HANDLE
process
);
HMENU
WINAPI
NtUserGetSystemMenu
(
HWND
hwnd
,
BOOL
revert
);
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment