mirror of
https://github.com/JetBrains/JetBrainsRuntime.git
synced 2025-12-06 09:29:38 +01:00
Check XInput extension && touch inertia
(cherry picked from commitcca7fb97f4) (cherry picked from commitedcb7310d3)
This commit is contained in:
@@ -59,7 +59,9 @@ public class XBaseWindow {
|
||||
VISIBLE = "visible", // whether it is visible by default
|
||||
SAVE_UNDER = "save under", // save content under this window
|
||||
BACKING_STORE = "backing store", // enables double buffering
|
||||
BIT_GRAVITY = "bit gravity"; // copy old content on geometry change
|
||||
BIT_GRAVITY = "bit gravity", // copy old content on geometry change
|
||||
XI_EVENT_MASK = "xi event mask", // xi event mask, Long
|
||||
XI_DEVICE_ID = "xi device id"; // xi device id, Integer
|
||||
private XCreateWindowParams delayedParams;
|
||||
|
||||
Set<Long> children = new HashSet<Long>();
|
||||
@@ -265,9 +267,6 @@ public class XBaseWindow {
|
||||
|
||||
public XBaseWindow (XCreateWindowParams params) {
|
||||
init(params);
|
||||
|
||||
// TODO find propper place for setup
|
||||
XlibWrapper.SetupXI2(XToolkit.getDisplay(), window);
|
||||
}
|
||||
|
||||
/* This create is used by the XEmbeddedFramePeer since it has to create the window
|
||||
@@ -405,6 +404,19 @@ public class XBaseWindow {
|
||||
throw new IllegalStateException("Couldn't create window because of wrong parameters. Run with NOISY_AWT to see details");
|
||||
}
|
||||
XToolkit.addToWinMap(window, this);
|
||||
|
||||
Long xiEventMask = (Long)params.get(XI_EVENT_MASK);
|
||||
if (xiEventMask != null && XToolkit.isXInputEnabled()) {
|
||||
Integer xiDeviceId = (Integer)params.get(XI_DEVICE_ID);
|
||||
if (xiDeviceId == null) {
|
||||
xiDeviceId = XConstants.XIAllDevices;
|
||||
}
|
||||
|
||||
int status = XToolkit.XISelectEvents(XToolkit.getDisplay(), window, xiEventMask, xiDeviceId);
|
||||
if (status != XConstants.Success) {
|
||||
throw new IllegalStateException("Couldn't select XI events. Status: " + status);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
xattr.dispose();
|
||||
}
|
||||
@@ -1134,18 +1146,14 @@ public class XBaseWindow {
|
||||
target = XToolkit.windowToXWindow(ev.get_xany().get_window());
|
||||
}
|
||||
|
||||
if (target == null && ev.get_type() == XConstants.GenericEvent) {
|
||||
if (XlibWrapper.XGetEventData(ev.get_xgeneric().get_display(), ev.pData)) {
|
||||
target = XToolkit.windowToXWindow(XlibWrapper.GetXIDeviceEvent(ev.get_xcookie()).get_event());
|
||||
}
|
||||
if (target == null && ev.get_type() == XConstants.GenericEvent &&
|
||||
XlibWrapper.XGetEventData(ev.get_xgeneric().get_display(), ev.pData)) {
|
||||
target = XToolkit.windowToXWindow(XToolkit.GetXIDeviceEvent(ev.get_xcookie()).get_event());
|
||||
}
|
||||
|
||||
if (target != null && target.checkInitialised()) {
|
||||
target.dispatchEvent(ev);
|
||||
}
|
||||
|
||||
// finally
|
||||
XlibWrapper.XFreeEventData(ev.get_xgeneric().get_display(), ev.pData);
|
||||
}
|
||||
|
||||
public void dispatchEvent(XEvent xev) {
|
||||
@@ -1210,8 +1218,15 @@ public class XBaseWindow {
|
||||
handleCreateNotify(xev);
|
||||
break;
|
||||
case XConstants.GenericEvent:
|
||||
// TODO add XI switch
|
||||
handleTouchEvent(xev);
|
||||
switch (xev.get_xgeneric().get_evtype()) {
|
||||
case XConstants.XI_TouchBegin:
|
||||
case XConstants.XI_TouchUpdate:
|
||||
case XConstants.XI_TouchEnd:
|
||||
handleTouchEvent(xev);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -682,6 +682,10 @@ public final class XConstants {
|
||||
public static final long XkbModifierMapMask = (1L<<2);
|
||||
public static final long XkbVirtualModsMask = (1L<<6); //server map
|
||||
|
||||
/* Fake device ID's for event selection */
|
||||
public static final int XIAllDevices = 0;
|
||||
public static final int XIAllMasterDevices = 1;
|
||||
|
||||
/* XI Event types */
|
||||
public static final int XI_DeviceChanged = 1;
|
||||
public static final int XI_KeyPress = 2;
|
||||
@@ -718,30 +722,30 @@ public final class XConstants {
|
||||
* Note: the protocol spec defines a mask to be of (1 << type). Clients are
|
||||
* free to create masks by bitshifting instead of using these defines.
|
||||
*/
|
||||
public static final int XI_DeviceChangedMask = 1 << XI_DeviceChanged;
|
||||
public static final int XI_KeyPressMask = 1 << XI_KeyPress;
|
||||
public static final int XI_KeyReleaseMask = 1 << XI_KeyRelease;
|
||||
public static final int XI_ButtonPressMask = 1 << XI_ButtonPress;
|
||||
public static final int XI_ButtonReleaseMask = 1 << XI_ButtonRelease;
|
||||
public static final int XI_MotionMask = 1 << XI_Motion;
|
||||
public static final int XI_EnterMask = 1 << XI_Enter;
|
||||
public static final int XI_LeaveMask = 1 << XI_Leave;
|
||||
public static final int XI_FocusInMask = 1 << XI_FocusIn;
|
||||
public static final int XI_FocusOutMask = 1 << XI_FocusOut;
|
||||
public static final int XI_HierarchyChangedMask = 1 << XI_HierarchyChanged;
|
||||
public static final int XI_PropertyEventMask = 1 << XI_PropertyEvent;
|
||||
public static final int XI_RawKeyPressMask = 1 << XI_RawKeyPress;
|
||||
public static final int XI_RawKeyReleaseMask = 1 << XI_RawKeyRelease;
|
||||
public static final int XI_RawButtonPressMask = 1 << XI_RawButtonPress;
|
||||
public static final int XI_RawButtonReleaseMask = 1 << XI_RawButtonRelease;
|
||||
public static final int XI_RawMotionMask = 1 << XI_RawMotion;
|
||||
public static final int XI_TouchBeginMask = 1 << XI_TouchBegin;
|
||||
public static final int XI_TouchEndMask = 1 << XI_TouchEnd;
|
||||
public static final int XI_TouchOwnershipChangedMask = 1 << XI_TouchOwnership;
|
||||
public static final int XI_TouchUpdateMask = 1 << XI_TouchUpdate;
|
||||
public static final int XI_RawTouchBeginMask = 1 << XI_RawTouchBegin;
|
||||
public static final int XI_RawTouchEndMask = 1 << XI_RawTouchEnd;
|
||||
public static final int XI_RawTouchUpdateMask = 1 << XI_RawTouchUpdate;
|
||||
public static final int XI_BarrierHitMask = 1 << XI_BarrierHit;
|
||||
public static final int XI_BarrierLeaveMask = 1 << XI_BarrierLeave;
|
||||
public static final long XI_DeviceChangedMask = 1L << XI_DeviceChanged;
|
||||
public static final long XI_KeyPressMask = 1L << XI_KeyPress;
|
||||
public static final long XI_KeyReleaseMask = 1L << XI_KeyRelease;
|
||||
public static final long XI_ButtonPressMask = 1L << XI_ButtonPress;
|
||||
public static final long XI_ButtonReleaseMask = 1L << XI_ButtonRelease;
|
||||
public static final long XI_MotionMask = 1L << XI_Motion;
|
||||
public static final long XI_EnterMask = 1L << XI_Enter;
|
||||
public static final long XI_LeaveMask = 1L << XI_Leave;
|
||||
public static final long XI_FocusInMask = 1L << XI_FocusIn;
|
||||
public static final long XI_FocusOutMask = 1L << XI_FocusOut;
|
||||
public static final long XI_HierarchyChangedMask = 1L << XI_HierarchyChanged;
|
||||
public static final long XI_PropertyEventMask = 1L << XI_PropertyEvent;
|
||||
public static final long XI_RawKeyPressMask = 1L << XI_RawKeyPress;
|
||||
public static final long XI_RawKeyReleaseMask = 1L << XI_RawKeyRelease;
|
||||
public static final long XI_RawButtonPressMask = 1L << XI_RawButtonPress;
|
||||
public static final long XI_RawButtonReleaseMask = 1L << XI_RawButtonRelease;
|
||||
public static final long XI_RawMotionMask = 1L << XI_RawMotion;
|
||||
public static final long XI_TouchBeginMask = 1L << XI_TouchBegin;
|
||||
public static final long XI_TouchEndMask = 1L << XI_TouchEnd;
|
||||
public static final long XI_TouchOwnershipChangedMask = 1L << XI_TouchOwnership;
|
||||
public static final long XI_TouchUpdateMask = 1L << XI_TouchUpdate;
|
||||
public static final long XI_RawTouchBeginMask = 1L << XI_RawTouchBegin;
|
||||
public static final long XI_RawTouchEndMask = 1L << XI_RawTouchEnd;
|
||||
public static final long XI_RawTouchUpdateMask = 1L << XI_RawTouchUpdate;
|
||||
public static final long XI_BarrierHitMask = 1L << XI_BarrierHit;
|
||||
public static final long XI_BarrierLeaveMask = 1L << XI_BarrierLeave;
|
||||
}
|
||||
|
||||
@@ -316,6 +316,7 @@ public final class XToolkit extends UNIXToolkit implements Runnable {
|
||||
log.finer("X locale modifiers are not supported, using default");
|
||||
}
|
||||
tryXKB();
|
||||
checkXInput();
|
||||
|
||||
arrowCursor = XlibWrapper.XCreateFontCursor(XToolkit.getDisplay(),
|
||||
XCursorFontConstants.XC_arrow);
|
||||
@@ -716,6 +717,8 @@ public final class XToolkit extends UNIXToolkit implements Runnable {
|
||||
XBaseWindow.ungrabInput();
|
||||
processException(thr);
|
||||
} finally {
|
||||
// free event data if XGetEventData was called
|
||||
XlibWrapper.XFreeEventData(getDisplay(), ev.pData);
|
||||
awtUnlock();
|
||||
}
|
||||
}
|
||||
@@ -2368,6 +2371,65 @@ public final class XToolkit extends UNIXToolkit implements Runnable {
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean hasXInputExtension = false;
|
||||
|
||||
public static boolean isXInputEnabled() {
|
||||
awtLock();
|
||||
try {
|
||||
return hasXInputExtension;
|
||||
} finally {
|
||||
awtUnlock();
|
||||
}
|
||||
}
|
||||
|
||||
public static void checkXInput() {
|
||||
awtLock();
|
||||
try {
|
||||
String extensionName = "XInputExtension";
|
||||
boolean hasExtension = XlibWrapper.XQueryExtension(XToolkit.getDisplay(), extensionName,
|
||||
XlibWrapper.iarg1, XlibWrapper.iarg2, XlibWrapper.iarg3);
|
||||
if (!hasExtension) {
|
||||
log.warning("X Input extension isn't available, error: {0}", Native.getInt(XlibWrapper.iarg1));
|
||||
return;
|
||||
}
|
||||
|
||||
// checking for 2.2 version
|
||||
final int requiredMajor = 2;
|
||||
final int requiredMinor = 2;
|
||||
Native.putInt(XlibWrapper.iarg1, requiredMajor);
|
||||
Native.putInt(XlibWrapper.iarg2, requiredMinor);
|
||||
int status = XlibWrapper.XIQueryVersion(XToolkit.getDisplay(), XlibWrapper.iarg1, XlibWrapper.iarg2);
|
||||
if (status == XConstants.BadRequest) {
|
||||
log.warning("X Input2 not supported in the server");
|
||||
return;
|
||||
}
|
||||
|
||||
int major = Native.getInt(XlibWrapper.iarg1);
|
||||
int minor = Native.getInt(XlibWrapper.iarg2);
|
||||
if (major >= requiredMajor && minor >= requiredMinor) {
|
||||
hasXInputExtension = true;
|
||||
} else {
|
||||
log.warning("Desired version is 2.2, server version is {0}.{1}", major, minor);
|
||||
}
|
||||
} finally {
|
||||
awtUnlock();
|
||||
}
|
||||
}
|
||||
|
||||
public static XIDeviceEvent GetXIDeviceEvent(XGenericEventCookie cookie) {
|
||||
return new XIDeviceEvent(cookie.get_data());
|
||||
}
|
||||
|
||||
// Use this one instead of native version
|
||||
public static int XISelectEvents(long display, long window, long mask, int deviceid) {
|
||||
if (isXInputEnabled()) {
|
||||
return XlibWrapper.XISelectEvents(display, window, mask, deviceid);
|
||||
} else {
|
||||
log.warning("Attempting to select xi events while xinput isn't available");
|
||||
return XConstants.BadRequest;
|
||||
}
|
||||
}
|
||||
|
||||
private static long eventNumber;
|
||||
public static long getEventNumber() {
|
||||
awtLock();
|
||||
|
||||
@@ -79,6 +79,14 @@ class XWindow extends XBaseWindow implements X11ComponentPeer {
|
||||
static long lastButton = 0;
|
||||
static WeakReference<XWindow> lastWindowRef = null;
|
||||
static int clickCount = 0;
|
||||
static int touchUpdates = 0;
|
||||
private static int touchBeginX = 0, touchBeginY = 0;
|
||||
private static final int TOUCH_CLICK_RADIUS = 2;
|
||||
private static final int TOUCH_UPDATES_THRESHOLD = 2;
|
||||
// all touch scrolls are measured in pixels
|
||||
private static final int TOUCH_BEGIN = 2;
|
||||
private static final int TOUCH_UPDATE = 3;
|
||||
private static final int TOUCH_END = 4;
|
||||
|
||||
// used to check if we need to re-create surfaceData.
|
||||
int oldWidth = -1;
|
||||
@@ -239,6 +247,11 @@ class XWindow extends XBaseWindow implements X11ComponentPeer {
|
||||
|
||||
params.putIfNull(BACKING_STORE, XToolkit.getBackingStoreType());
|
||||
|
||||
params.putIfNull(XI_EVENT_MASK, XConstants.XI_TouchBeginMask |
|
||||
XConstants.XI_TouchUpdateMask |
|
||||
XConstants.XI_TouchEndMask);
|
||||
params.putIfNull(XI_DEVICE_ID, XConstants.XIAllMasterDevices);
|
||||
|
||||
XToolkit.awtLock();
|
||||
try {
|
||||
if (wm_protocols == null) {
|
||||
@@ -780,61 +793,92 @@ class XWindow extends XBaseWindow implements X11ComponentPeer {
|
||||
}
|
||||
|
||||
public void handleTouchEvent(XEvent xev) {
|
||||
XIDeviceEvent dev = XlibWrapper.GetXIDeviceEvent(xev.get_xcookie());
|
||||
super.handleTouchEvent(xev);
|
||||
|
||||
XIDeviceEvent dev = XToolkit.GetXIDeviceEvent(xev.get_xcookie());
|
||||
int x = scaleDown((int) dev.get_event_x());
|
||||
int y = scaleDown((int) dev.get_event_y());
|
||||
|
||||
// TODO do we need this like in in mouse event handler
|
||||
if (dev.get_event() != window) {
|
||||
Point localXY = toLocal(x, y);
|
||||
x = localXY.x;
|
||||
y = localXY.y;
|
||||
}
|
||||
|
||||
long when = dev.get_time();
|
||||
long jWhen = XToolkit.nowMillisUTC_offset(when);
|
||||
// TODO do we need any other button?
|
||||
int button = XConstants.buttons[0];
|
||||
int modifiers = getModifiers(dev.get_mods().get_effective(), button, 0);
|
||||
// turning off shift modifier
|
||||
modifiers &= ~InputEvent.SHIFT_DOWN_MASK;
|
||||
|
||||
long jWhen = XToolkit.nowMillisUTC_offset(dev.get_time());
|
||||
|
||||
switch (dev.get_evtype()) {
|
||||
case XConstants.XI_TouchBegin:
|
||||
touchUpdates = 0;
|
||||
touchBeginX = x;
|
||||
touchBeginY = y;
|
||||
sendWheelEventFromTouch(dev, jWhen, modifiers, x, y, TOUCH_BEGIN, 1);
|
||||
break;
|
||||
case XConstants.XI_TouchUpdate:
|
||||
int direction = y >= lastY ? -1 : 1;
|
||||
int modifiers = 0;
|
||||
int scrollAmount = Math.abs(lastY - y);
|
||||
|
||||
if (scrollAmount < Math.abs(lastX - x)) {
|
||||
scrollAmount = Math.abs(lastX - x);
|
||||
modifiers |= InputEvent.SHIFT_DOWN_MASK;
|
||||
direction = x >= lastX ? -1 : 1;
|
||||
if (isInsideTouchClickBoundaries(x, y)) {
|
||||
break;
|
||||
}
|
||||
++touchUpdates;
|
||||
|
||||
if (scrollAmount < 1) {
|
||||
// workaround to distinguish touch move and touch click
|
||||
if (touchUpdates < TOUCH_UPDATES_THRESHOLD) {
|
||||
break;
|
||||
}
|
||||
|
||||
MouseWheelEvent mwe = new MouseWheelEvent(getEventSource(), MouseEvent.MOUSE_WHEEL, jWhen,
|
||||
if (lastY - y != 0) {
|
||||
sendWheelEventFromTouch(dev, jWhen, modifiers, x, y, TOUCH_UPDATE, lastY - y);
|
||||
}
|
||||
if (lastX - x != 0) {
|
||||
// horizontal scroll
|
||||
modifiers |= InputEvent.SHIFT_DOWN_MASK;
|
||||
sendWheelEventFromTouch(dev, jWhen, modifiers, x, y, TOUCH_UPDATE, lastX - x);
|
||||
}
|
||||
break;
|
||||
case XConstants.XI_TouchEnd:
|
||||
if (touchUpdates < TOUCH_UPDATES_THRESHOLD) {
|
||||
sendMouseEventFromTouch(dev, MouseEvent.MOUSE_PRESSED, jWhen, modifiers, x, y, button);
|
||||
sendMouseEventFromTouch(dev, MouseEvent.MOUSE_RELEASED, jWhen, modifiers, x, y, button);
|
||||
sendMouseEventFromTouch(dev, MouseEvent.MOUSE_CLICKED, jWhen, modifiers, x, y, button);
|
||||
}
|
||||
sendWheelEventFromTouch(dev, jWhen, modifiers, x, y, TOUCH_END, 1);
|
||||
break;
|
||||
}
|
||||
|
||||
lastX = x;
|
||||
lastY = y;
|
||||
}
|
||||
|
||||
private boolean isInsideTouchClickBoundaries(int x, int y) {
|
||||
return Math.abs(touchBeginX - x) <= TOUCH_CLICK_RADIUS &&
|
||||
Math.abs(touchBeginY - y) <= TOUCH_CLICK_RADIUS;
|
||||
}
|
||||
|
||||
private void sendWheelEventFromTouch(XIDeviceEvent dev, long jWhen, int modifiers, int x, int y, int type, int delta) {
|
||||
postEventToEventQueue(
|
||||
new MouseWheelEvent(getEventSource(), MouseEvent.MOUSE_WHEEL, jWhen,
|
||||
modifiers,
|
||||
x, y,
|
||||
scaleDown((int) dev.get_root_x()),
|
||||
scaleDown((int) dev.get_root_y()),
|
||||
1, false, MouseWheelEvent.WHEEL_UNIT_SCROLL,
|
||||
scrollAmount, direction);
|
||||
postEventToEventQueue(mwe);
|
||||
0, false, type,
|
||||
1, delta));
|
||||
}
|
||||
|
||||
lastX = x;
|
||||
lastY = y;
|
||||
break;
|
||||
case XConstants.XI_TouchBegin:
|
||||
case XConstants.XI_TouchEnd:
|
||||
// TODO add click events
|
||||
|
||||
lastX = x;
|
||||
lastY = y;
|
||||
break;
|
||||
default:
|
||||
// TODO remove this
|
||||
System.out.println("Unknown");
|
||||
break;
|
||||
}
|
||||
private void sendMouseEventFromTouch(XIDeviceEvent dev, int type, long jWhen, int modifiers, int x, int y, int button) {
|
||||
postEventToEventQueue(
|
||||
new MouseEvent(getEventSource(), type, jWhen,
|
||||
modifiers,
|
||||
x, y,
|
||||
scaleDown((int) dev.get_root_x()),
|
||||
scaleDown((int) dev.get_root_y()),
|
||||
1,
|
||||
false, button));
|
||||
}
|
||||
|
||||
public void handleMotionNotify(XEvent xev) {
|
||||
|
||||
@@ -554,16 +554,14 @@ static native String XSetLocaleModifiers(String modifier_list);
|
||||
|
||||
static native void SetZOrder(long display, long window, long above);
|
||||
|
||||
// static native void XISetMask(long );
|
||||
// static native void XISelectEvents(long display, long window);
|
||||
//TODO replace it with more general func
|
||||
static native void SetupXI2(long display, long window);
|
||||
// use wrapped functions from XToolkit
|
||||
// instead these native functions
|
||||
// these functions don't check if XInput extension is available
|
||||
static native int XIQueryVersion(long display, long major_version_iptr, long minor_version_iptr);
|
||||
static native int XISelectEvents(long display, long window, long mask, int deviceid);
|
||||
|
||||
static native boolean XGetEventData(long display, long ptr);
|
||||
static native void XFreeEventData(long display, long ptr);
|
||||
static XIDeviceEvent GetXIDeviceEvent(XGenericEventCookie cookie) {
|
||||
return new XIDeviceEvent(cookie.get_data());
|
||||
}
|
||||
|
||||
/* Global memory area used for X lib parameter passing */
|
||||
|
||||
|
||||
@@ -2320,26 +2320,30 @@ Java_sun_awt_X11_XlibWrapper_SetZOrder
|
||||
|
||||
/*
|
||||
* Class: XlibWrapper
|
||||
* Method: SetupXI2
|
||||
* Method: XIQueryVersion
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_sun_awt_X11_XlibWrapper_SetupXI2
|
||||
(JNIEnv *env, jclass clazz, jlong display, jlong window)
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_sun_awt_X11_XlibWrapper_XIQueryVersion
|
||||
(JNIEnv *env, jclass clazz, jlong display, jlong major_version_iptr, jlong minor_version_iptr)
|
||||
{
|
||||
unsigned char mask[XIMaskLen(XI_LASTEVENT)];
|
||||
memset(mask, 0, sizeof(mask));
|
||||
|
||||
XISetMask(mask, XI_TouchBegin);
|
||||
XISetMask(mask, XI_TouchUpdate);
|
||||
XISetMask(mask, XI_TouchEnd);
|
||||
return XIQueryVersion((Display *)jlong_to_ptr(display),
|
||||
jlong_to_ptr(major_version_iptr), jlong_to_ptr(minor_version_iptr));
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: XlibWrapper
|
||||
* Method: XISelectEvents
|
||||
*/
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_sun_awt_X11_XlibWrapper_XISelectEvents
|
||||
(JNIEnv *env, jclass clazz, jlong display, jlong window, jlong mask, jint deviceid)
|
||||
{
|
||||
XIEventMask evmask;
|
||||
// TODO make deviceid filtering
|
||||
evmask.deviceid = XIAllMasterDevices;
|
||||
evmask.mask_len = sizeof(mask);
|
||||
evmask.mask = mask;
|
||||
XISelectEvents((Display *)jlong_to_ptr(display), (Window)jlong_to_ptr(window), &evmask, 1);
|
||||
XFlush((Display *)jlong_to_ptr(display));
|
||||
evmask.deviceid = (int)deviceid;
|
||||
evmask.mask_len = XIMaskLen(XI_LASTEVENT);
|
||||
// FIXME potential byte order problem
|
||||
evmask.mask = &mask;
|
||||
return XISelectEvents((Display *)jlong_to_ptr(display), (Window)jlong_to_ptr(window), &evmask, 1);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
Reference in New Issue
Block a user