mirror of
https://github.com/gwm17/glfw.git
synced 2024-11-26 20:28:49 -05:00
parent
67c6a45e0e
commit
9c315412e1
|
@ -344,17 +344,20 @@ if (_GLFW_COCOA AND _GLFW_NSGL)
|
|||
find_library(IOKIT_FRAMEWORK IOKit)
|
||||
find_library(CORE_FOUNDATION_FRAMEWORK CoreFoundation)
|
||||
find_library(CORE_VIDEO_FRAMEWORK CoreVideo)
|
||||
find_library(CARBON_FRAMEWORK Carbon)
|
||||
mark_as_advanced(COCOA_FRAMEWORK
|
||||
IOKIT_FRAMEWORK
|
||||
CORE_FOUNDATION_FRAMEWORK
|
||||
CORE_VIDEO_FRAMEWORK)
|
||||
CORE_VIDEO_FRAMEWORK
|
||||
CARBON_FRAMEWORK)
|
||||
list(APPEND glfw_LIBRARIES "${COCOA_FRAMEWORK}"
|
||||
"${IOKIT_FRAMEWORK}"
|
||||
"${CORE_FOUNDATION_FRAMEWORK}"
|
||||
"${CORE_VIDEO_FRAMEWORK}")
|
||||
"${CORE_VIDEO_FRAMEWORK}"
|
||||
"${CARBON_FRAMEWORK}")
|
||||
|
||||
set(glfw_PKG_DEPS "")
|
||||
set(glfw_PKG_LIBS "-framework Cocoa -framework IOKit -framework CoreFoundation -framework CoreVideo")
|
||||
set(glfw_PKG_LIBS "-framework Cocoa -framework IOKit -framework CoreFoundation -framework CoreVideo -framework Carbon")
|
||||
endif()
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
|
|
|
@ -66,6 +66,8 @@ used by the tests and examples and are not required to build the library.
|
|||
|
||||
- Added `glfwSetWindowSizeLimits` and `glfwSetWindowAspectRatio` for setting
|
||||
absolute and relative window size limits
|
||||
- Added `glfwGetKeyName` for querying the layout-specific name of printable
|
||||
keys
|
||||
- Added `GLFW_NO_API` for creating window without contexts
|
||||
- Added `GLFW_CONTEXT_NO_ERROR` context hint for `GL_KHR_no_error` support
|
||||
- Added `GLFW_TRUE` and `GLFW_FALSE` as client API independent boolean values
|
||||
|
|
|
@ -192,6 +192,22 @@ void charmods_callback(GLFWwindow* window, unsigned int codepoint, int mods)
|
|||
@endcode
|
||||
|
||||
|
||||
@subsection input_key_name Key names
|
||||
|
||||
If you wish to refer to keys by name, you can query the keyboard layout
|
||||
dependent name of printable keys with @ref glfwGetKeyName.
|
||||
|
||||
@code
|
||||
const char* key_name = glfwGetKeyName(GLFW_KEY_W, 0);
|
||||
show_tutorial_hint("Press %s to move forward", key_name);
|
||||
@endcode
|
||||
|
||||
This function can handle both [keys and scancodes](@ref input_key). If the
|
||||
specified key is `GLFW_KEY_UNKNOWN` then the scancode is used, otherwise it is
|
||||
ignored. This matches the behavior of the key callback, meaning the callback
|
||||
arguments can always be passed unmodified to this function.
|
||||
|
||||
|
||||
@section input_mouse Mouse input
|
||||
|
||||
Mouse input comes in many forms, including cursor motion, button presses and
|
||||
|
|
|
@ -11,6 +11,12 @@ GLFW now supports setting both absolute and relative window size limits with
|
|||
@ref glfwSetWindowSizeLimits and @ref glfwSetWindowAspectRatio.
|
||||
|
||||
|
||||
@subsection news_32_keyname Localized key names
|
||||
|
||||
GLFW now supports querying the localized name of printable keys with @ref
|
||||
glfwGetKeyName, either by key token or by scancode.
|
||||
|
||||
|
||||
@section news_31 New features in 3.1
|
||||
|
||||
These are the release highlights. For a full list of changes see the
|
||||
|
|
|
@ -414,6 +414,7 @@ extern "C" {
|
|||
#define GLFW_KEY_RIGHT_ALT 346
|
||||
#define GLFW_KEY_RIGHT_SUPER 347
|
||||
#define GLFW_KEY_MENU 348
|
||||
|
||||
#define GLFW_KEY_LAST GLFW_KEY_MENU
|
||||
|
||||
/*! @} */
|
||||
|
@ -2588,6 +2589,33 @@ GLFWAPI int glfwGetInputMode(GLFWwindow* window, int mode);
|
|||
*/
|
||||
GLFWAPI void glfwSetInputMode(GLFWwindow* window, int mode, int value);
|
||||
|
||||
/*! @brief Returns the localized name of the specified printable key.
|
||||
*
|
||||
* This function returns the localized name of the specified printable key.
|
||||
*
|
||||
* If the key is `GLFW_KEY_UNKNOWN`, the scancode is used, otherwise the
|
||||
* scancode is ignored.
|
||||
*
|
||||
* @param[in] key The key to query, or `GLFW_KEY_UNKNOWN`.
|
||||
* @param[in] scancode The scancode of the key to query.
|
||||
* @return The localized name of the key.
|
||||
*
|
||||
* @par Pointer Lifetime
|
||||
* The returned string is allocated and freed by GLFW. You should not free it
|
||||
* yourself. It is valid until the next call to @ref glfwGetKeyName, or until
|
||||
* the library is terminated.
|
||||
*
|
||||
* @par Thread Safety
|
||||
* This function may only be called from the main thread.
|
||||
*
|
||||
* @sa @ref input_key_name
|
||||
*
|
||||
* @since Added in GLFW 3.2.
|
||||
*
|
||||
* @ingroup input
|
||||
*/
|
||||
GLFWAPI const char* glfwGetKeyName(int key, int scancode);
|
||||
|
||||
/*! @brief Returns the last reported state of a keyboard key for the specified
|
||||
* window.
|
||||
*
|
||||
|
@ -2607,6 +2635,8 @@ GLFWAPI void glfwSetInputMode(GLFWwindow* window, int mode, int value);
|
|||
* The [modifier key bit masks](@ref mods) are not key tokens and cannot be
|
||||
* used with this function.
|
||||
*
|
||||
* __Do not use this function__ to implement [text input](@ref input_char).
|
||||
*
|
||||
* @param[in] window The desired window.
|
||||
* @param[in] key The desired [keyboard key](@ref keys). `GLFW_KEY_UNKNOWN` is
|
||||
* not a valid key for this function.
|
||||
|
|
|
@ -72,7 +72,10 @@ static void changeToResourcesDirectory(void)
|
|||
//
|
||||
static void createKeyTables(void)
|
||||
{
|
||||
int scancode;
|
||||
|
||||
memset(_glfw.ns.publicKeys, -1, sizeof(_glfw.ns.publicKeys));
|
||||
memset(_glfw.ns.nativeKeys, -1, sizeof(_glfw.ns.nativeKeys));
|
||||
|
||||
_glfw.ns.publicKeys[0x1D] = GLFW_KEY_0;
|
||||
_glfw.ns.publicKeys[0x12] = GLFW_KEY_1;
|
||||
|
@ -188,6 +191,13 @@ static void createKeyTables(void)
|
|||
_glfw.ns.publicKeys[0x51] = GLFW_KEY_KP_EQUAL;
|
||||
_glfw.ns.publicKeys[0x43] = GLFW_KEY_KP_MULTIPLY;
|
||||
_glfw.ns.publicKeys[0x4E] = GLFW_KEY_KP_SUBTRACT;
|
||||
|
||||
for (scancode = 0; scancode < 256; scancode++)
|
||||
{
|
||||
// Store the reverse translation for faster key name lookup
|
||||
if (_glfw.ns.publicKeys[scancode] >= 0)
|
||||
_glfw.ns.nativeKeys[_glfw.ns.publicKeys[scancode]] = scancode;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -211,6 +221,17 @@ int _glfwPlatformInit(void)
|
|||
|
||||
CGEventSourceSetLocalEventsSuppressionInterval(_glfw.ns.eventSource, 0.0);
|
||||
|
||||
// TODO: Catch kTISNotifySelectedKeyboardInputSourceChanged and update
|
||||
|
||||
_glfw.ns.inputSource = TISCopyCurrentKeyboardLayoutInputSource();
|
||||
if (!_glfw.ns.inputSource)
|
||||
return GLFW_FALSE;
|
||||
|
||||
_glfw.ns.unicodeData = TISGetInputSourceProperty(_glfw.ns.inputSource,
|
||||
kTISPropertyUnicodeKeyLayoutData);
|
||||
if (!_glfw.ns.unicodeData)
|
||||
return GLFW_FALSE;
|
||||
|
||||
if (!_glfwInitContextAPI())
|
||||
return GLFW_FALSE;
|
||||
|
||||
|
@ -222,6 +243,12 @@ int _glfwPlatformInit(void)
|
|||
|
||||
void _glfwPlatformTerminate(void)
|
||||
{
|
||||
if (_glfw.ns.inputSource)
|
||||
{
|
||||
CFRelease(_glfw.ns.inputSource);
|
||||
_glfw.ns.inputSource = NULL;
|
||||
}
|
||||
|
||||
if (_glfw.ns.eventSource)
|
||||
{
|
||||
CFRelease(_glfw.ns.eventSource);
|
||||
|
|
|
@ -30,8 +30,10 @@
|
|||
#include <stdint.h>
|
||||
|
||||
#if defined(__OBJC__)
|
||||
#import <Carbon/Carbon.h>
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#else
|
||||
#include <Carbon/Carbon.h>
|
||||
#include <ApplicationServices/ApplicationServices.h>
|
||||
typedef void* id;
|
||||
#endif
|
||||
|
@ -73,13 +75,17 @@ typedef struct _GLFWwindowNS
|
|||
//
|
||||
typedef struct _GLFWlibraryNS
|
||||
{
|
||||
CGEventSourceRef eventSource;
|
||||
id delegate;
|
||||
id autoreleasePool;
|
||||
id cursor;
|
||||
CGEventSourceRef eventSource;
|
||||
id delegate;
|
||||
id autoreleasePool;
|
||||
id cursor;
|
||||
TISInputSourceRef inputSource;
|
||||
id unicodeData;
|
||||
|
||||
short int publicKeys[256];
|
||||
char* clipboardString;
|
||||
char keyName[64];
|
||||
short int publicKeys[256];
|
||||
short int nativeKeys[GLFW_KEY_LAST + 1];
|
||||
char* clipboardString;
|
||||
|
||||
} _GLFWlibraryNS;
|
||||
|
||||
|
|
|
@ -1168,6 +1168,48 @@ void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode)
|
|||
CGAssociateMouseAndMouseCursorPosition(true);
|
||||
}
|
||||
|
||||
const char* _glfwPlatformGetKeyName(int key, int scancode)
|
||||
{
|
||||
if (key != GLFW_KEY_UNKNOWN)
|
||||
scancode = _glfw.ns.nativeKeys[key];
|
||||
|
||||
if (!_glfwIsPrintable(_glfw.ns.publicKeys[scancode]))
|
||||
return NULL;
|
||||
|
||||
UInt32 deadKeyState = 0;
|
||||
UniChar characters[8];
|
||||
UniCharCount characterCount = 0;
|
||||
|
||||
if (UCKeyTranslate([(NSData*) _glfw.ns.unicodeData bytes],
|
||||
scancode,
|
||||
kUCKeyActionDisplay,
|
||||
0,
|
||||
LMGetKbdType(),
|
||||
kUCKeyTranslateNoDeadKeysBit,
|
||||
&deadKeyState,
|
||||
sizeof(characters) / sizeof(characters[0]),
|
||||
&characterCount,
|
||||
characters) != noErr)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!characterCount)
|
||||
return NULL;
|
||||
|
||||
CFStringRef string = CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault,
|
||||
characters,
|
||||
characterCount,
|
||||
kCFAllocatorNull);
|
||||
CFStringGetCString(string,
|
||||
_glfw.ns.keyName,
|
||||
sizeof(_glfw.ns.keyName),
|
||||
kCFStringEncodingUTF8);
|
||||
CFRelease(string);
|
||||
|
||||
return _glfw.ns.keyName;
|
||||
}
|
||||
|
||||
int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
|
||||
const GLFWimage* image,
|
||||
int xhot, int yhot)
|
||||
|
|
18
src/input.c
18
src/input.c
|
@ -223,6 +223,18 @@ void _glfwInputDrop(_GLFWwindow* window, int count, const char** paths)
|
|||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
////// GLFW internal API //////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
GLFWbool _glfwIsPrintable(int key)
|
||||
{
|
||||
return (key >= GLFW_KEY_APOSTROPHE && key <= GLFW_KEY_WORLD_2) ||
|
||||
(key >= GLFW_KEY_KP_0 && key <= GLFW_KEY_KP_ADD) ||
|
||||
key == GLFW_KEY_KP_EQUAL;
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
////// GLFW public API //////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
@ -270,6 +282,12 @@ GLFWAPI void glfwSetInputMode(GLFWwindow* handle, int mode, int value)
|
|||
}
|
||||
}
|
||||
|
||||
GLFWAPI const char* glfwGetKeyName(int key, int scancode)
|
||||
{
|
||||
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
|
||||
return _glfwPlatformGetKeyName(key, scancode);
|
||||
}
|
||||
|
||||
GLFWAPI int glfwGetKey(GLFWwindow* handle, int key)
|
||||
{
|
||||
_GLFWwindow* window = (_GLFWwindow*) handle;
|
||||
|
|
|
@ -428,6 +428,11 @@ void _glfwPlatformSetCursorPos(_GLFWwindow* window, double xpos, double ypos);
|
|||
*/
|
||||
void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode);
|
||||
|
||||
/*! @copydoc glfwGetKeyName
|
||||
* @ingroup platform
|
||||
*/
|
||||
const char* _glfwPlatformGetKeyName(int key, int scancode);
|
||||
|
||||
/*! @copydoc glfwGetMonitors
|
||||
* @ingroup platform
|
||||
*/
|
||||
|
@ -900,4 +905,8 @@ void _glfwFreeMonitor(_GLFWmonitor* monitor);
|
|||
*/
|
||||
void _glfwFreeMonitors(_GLFWmonitor** monitors, int count);
|
||||
|
||||
/*! @ingroup utility
|
||||
*/
|
||||
int _glfwIsPrintable(int key);
|
||||
|
||||
#endif // _glfw3_internal_h_
|
||||
|
|
|
@ -802,6 +802,13 @@ void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode)
|
|||
"Mir: Unsupported function %s", __PRETTY_FUNCTION__);
|
||||
}
|
||||
|
||||
const char* _glfwPlatformGetKeyName(int key, int scancode)
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Mir: Unsupported function %s", __PRETTY_FUNCTION__);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void _glfwPlatformSetClipboardString(_GLFWwindow* window, const char* string)
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
|
|
|
@ -130,7 +130,10 @@ static void terminateLibraries(void)
|
|||
//
|
||||
static void createKeyTables(void)
|
||||
{
|
||||
int scancode;
|
||||
|
||||
memset(_glfw.win32.publicKeys, -1, sizeof(_glfw.win32.publicKeys));
|
||||
memset(_glfw.win32.nativeKeys, -1, sizeof(_glfw.win32.nativeKeys));
|
||||
|
||||
_glfw.win32.publicKeys[0x00B] = GLFW_KEY_0;
|
||||
_glfw.win32.publicKeys[0x002] = GLFW_KEY_1;
|
||||
|
@ -252,6 +255,12 @@ static void createKeyTables(void)
|
|||
_glfw.win32.publicKeys[0x11C] = GLFW_KEY_KP_ENTER;
|
||||
_glfw.win32.publicKeys[0x037] = GLFW_KEY_KP_MULTIPLY;
|
||||
_glfw.win32.publicKeys[0x04A] = GLFW_KEY_KP_SUBTRACT;
|
||||
|
||||
for (scancode = 0; scancode < 512; scancode++)
|
||||
{
|
||||
if (_glfw.win32.publicKeys[scancode] > 0)
|
||||
_glfw.win32.nativeKeys[_glfw.win32.publicKeys[scancode]] = scancode;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -182,7 +182,9 @@ typedef struct _GLFWlibraryWin32
|
|||
{
|
||||
DWORD foregroundLockTimeout;
|
||||
char* clipboardString;
|
||||
char keyName[64];
|
||||
short int publicKeys[512];
|
||||
short int nativeKeys[GLFW_KEY_LAST + 1];
|
||||
|
||||
// winmm.dll
|
||||
struct {
|
||||
|
|
|
@ -1180,6 +1180,30 @@ void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode)
|
|||
SetCursor(NULL);
|
||||
}
|
||||
|
||||
const char* _glfwPlatformGetKeyName(int key, int scancode)
|
||||
{
|
||||
WCHAR name[16];
|
||||
|
||||
if (key != GLFW_KEY_UNKNOWN)
|
||||
scancode = _glfw.win32.nativeKeys[key];
|
||||
|
||||
if (!_glfwIsPrintable(_glfw.win32.publicKeys[scancode]))
|
||||
return NULL;
|
||||
|
||||
if (!GetKeyNameTextW(scancode << 16, name, sizeof(name) / sizeof(WCHAR)))
|
||||
return NULL;
|
||||
|
||||
if (!WideCharToMultiByte(CP_UTF8, 0, name, -1,
|
||||
_glfw.win32.keyName,
|
||||
sizeof(_glfw.win32.keyName),
|
||||
NULL, NULL))
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return _glfw.win32.keyName;
|
||||
}
|
||||
|
||||
int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
|
||||
const GLFWimage* image,
|
||||
int xhot, int yhot)
|
||||
|
|
|
@ -436,6 +436,12 @@ void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode)
|
|||
_glfwPlatformSetCursor(window, window->wl.currentCursor);
|
||||
}
|
||||
|
||||
const char* _glfwPlatformGetKeyName(int key, int scancode)
|
||||
{
|
||||
// TODO
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
|
||||
const GLFWimage* image,
|
||||
int xhot, int yhot)
|
||||
|
|
|
@ -234,6 +234,7 @@ static void createKeyTables(void)
|
|||
int scancode, key;
|
||||
|
||||
memset(_glfw.x11.publicKeys, -1, sizeof(_glfw.x11.publicKeys));
|
||||
memset(_glfw.x11.nativeKeys, -1, sizeof(_glfw.x11.nativeKeys));
|
||||
|
||||
if (_glfw.x11.xkb.available)
|
||||
{
|
||||
|
@ -312,12 +313,16 @@ static void createKeyTables(void)
|
|||
XkbFreeClientMap(desc, 0, True);
|
||||
}
|
||||
|
||||
// Translate the un-translated key codes using traditional X11 KeySym
|
||||
// lookups
|
||||
for (scancode = 0; scancode < 256; scancode++)
|
||||
{
|
||||
// Translate the un-translated key codes using traditional X11 KeySym
|
||||
// lookups
|
||||
if (_glfw.x11.publicKeys[scancode] < 0)
|
||||
_glfw.x11.publicKeys[scancode] = translateKeyCode(scancode);
|
||||
|
||||
// Store the reverse translation for faster key name lookup
|
||||
if (_glfw.x11.publicKeys[scancode] > 0)
|
||||
_glfw.x11.nativeKeys[_glfw.x11.publicKeys[scancode]] = scancode;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -121,8 +121,12 @@ typedef struct _GLFWlibraryX11
|
|||
int errorCode;
|
||||
// Clipboard string (while the selection is owned)
|
||||
char* clipboardString;
|
||||
// Key name string
|
||||
char keyName[64];
|
||||
// X11 keycode to GLFW key LUT
|
||||
short int publicKeys[256];
|
||||
// GLFW key to X11 keycode LUT
|
||||
short int nativeKeys[GLFW_KEY_LAST + 1];
|
||||
|
||||
// Window manager atoms
|
||||
Atom WM_PROTOCOLS;
|
||||
|
|
|
@ -1939,6 +1939,34 @@ void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode)
|
|||
}
|
||||
}
|
||||
|
||||
const char* _glfwPlatformGetKeyName(int key, int scancode)
|
||||
{
|
||||
KeySym keysym;
|
||||
int extra;
|
||||
|
||||
if (!_glfw.x11.xkb.available)
|
||||
return NULL;
|
||||
|
||||
if (key != GLFW_KEY_UNKNOWN)
|
||||
scancode = _glfw.x11.nativeKeys[key];
|
||||
|
||||
if (!_glfwIsPrintable(_glfw.x11.publicKeys[scancode]))
|
||||
return NULL;
|
||||
|
||||
keysym = XkbKeycodeToKeysym(_glfw.x11.display, scancode, 0, 0);
|
||||
if (keysym == NoSymbol)
|
||||
return NULL;
|
||||
|
||||
XkbTranslateKeySym(_glfw.x11.display, &keysym, 0,
|
||||
_glfw.x11.keyName, sizeof(_glfw.x11.keyName),
|
||||
&extra);
|
||||
|
||||
if (!strlen(_glfw.x11.keyName))
|
||||
return NULL;
|
||||
|
||||
return _glfw.x11.keyName;
|
||||
}
|
||||
|
||||
int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
|
||||
const GLFWimage* image,
|
||||
int xhot, int yhot)
|
||||
|
|
|
@ -360,12 +360,25 @@ static void scroll_callback(GLFWwindow* window, double x, double y)
|
|||
static void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods)
|
||||
{
|
||||
Slot* slot = glfwGetWindowUserPointer(window);
|
||||
const char* name = glfwGetKeyName(key, scancode);
|
||||
|
||||
printf("%08x to %i at %0.3f: Key 0x%04x Scancode 0x%04x (%s) (with%s) was %s\n",
|
||||
counter++, slot->number, glfwGetTime(), key, scancode,
|
||||
get_key_name(key),
|
||||
get_mods_name(mods),
|
||||
get_action_name(action));
|
||||
if (name)
|
||||
{
|
||||
printf("%08x to %i at %0.3f: Key 0x%04x Scancode 0x%04x (%s) (%s) (with%s) was %s\n",
|
||||
counter++, slot->number, glfwGetTime(), key, scancode,
|
||||
get_key_name(key),
|
||||
name,
|
||||
get_mods_name(mods),
|
||||
get_action_name(action));
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("%08x to %i at %0.3f: Key 0x%04x Scancode 0x%04x (%s) (with%s) was %s\n",
|
||||
counter++, slot->number, glfwGetTime(), key, scancode,
|
||||
get_key_name(key),
|
||||
get_mods_name(mods),
|
||||
get_action_name(action));
|
||||
}
|
||||
|
||||
if (action != GLFW_PRESS)
|
||||
return;
|
||||
|
|
Loading…
Reference in New Issue
Block a user