hopefully fix XCheckPred problem (PR 8547), and add support with -singleInstance
svn: r5701
This commit is contained in:
parent
75a0a02355
commit
efe9e73e8e
|
@ -118,6 +118,9 @@ static Scheme_Thread *user_main_thread;
|
|||
|
||||
extern void wxMediaIOCheckLSB(void);
|
||||
extern void wxMouseEventHandled(void);
|
||||
#ifdef wx_xt
|
||||
extern int wx_single_instance;
|
||||
#endif
|
||||
|
||||
#include "mred.h"
|
||||
|
||||
|
@ -3416,7 +3419,14 @@ void MrEdApp::RealInit(void)
|
|||
scheme_exit = CAST_EXIT MrEdExit;
|
||||
#endif
|
||||
|
||||
exit_val = mred_finish_cmd_line_run();
|
||||
#ifdef wx_xt
|
||||
if (wx_single_instance) {
|
||||
exit_val = wxCheckSingleInstance(global_env);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!exit_val)
|
||||
exit_val = mred_finish_cmd_line_run();
|
||||
|
||||
scheme_kill_thread(scheme_current_thread);
|
||||
}
|
||||
|
@ -3809,10 +3819,9 @@ static unsigned long get_deeper_base()
|
|||
#endif
|
||||
|
||||
/****************************************************************************/
|
||||
/* Mac AE support */
|
||||
/* AE-like support */
|
||||
/****************************************************************************/
|
||||
|
||||
#if defined(wx_mac) || defined(wx_msw)
|
||||
static void wxDo(Scheme_Object *proc, int argc, Scheme_Object **argv)
|
||||
{
|
||||
mz_jmp_buf * volatile save, newbuf;
|
||||
|
@ -3852,11 +3861,16 @@ void wxDrop_Runtime(char **argv, int argc)
|
|||
|
||||
for (i = 0; i < argc; i++) {
|
||||
Scheme_Object *p[1];
|
||||
#ifdef wx_xt
|
||||
p[0] = scheme_char_string_to_path(scheme_make_utf8_string(argv[i]));
|
||||
#else
|
||||
p[0] = scheme_make_path(argv[i]);
|
||||
#endif
|
||||
wxDo(wxs_app_file_proc, 1, p);
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(wx_mac) || defined(wx_msw)
|
||||
void wxDrop_Quit()
|
||||
{
|
||||
#if WINDOW_STDIO
|
||||
|
|
|
@ -228,4 +228,5 @@ extern void WakeUpMrEd();
|
|||
|
||||
#if defined(wx_xt)
|
||||
extern void wxUnhideAllCursors();
|
||||
int wxCheckSingleInstance(Scheme_Env *global_env);
|
||||
#endif
|
||||
|
|
|
@ -35,6 +35,9 @@ static int grab_stack_pos = 0, grab_stack_size = 0;
|
|||
|
||||
extern Widget wx_clipWindow, wx_selWindow;
|
||||
|
||||
Window wxAddClipboardWindowProperty(Atom prop);
|
||||
extern Atom wx_single_instance_tag;
|
||||
|
||||
wxWindow *wxLocationToWindow(int x, int y);
|
||||
|
||||
extern "C" {
|
||||
|
@ -152,6 +155,41 @@ static Window GetEventWindow(XEvent *e)
|
|||
static unsigned long lastUngrabTime;
|
||||
static unsigned long lastUnhideTime;
|
||||
|
||||
class Check_Ungrab_Record {
|
||||
public:
|
||||
Window window;
|
||||
int x, y, x_root, y_root;
|
||||
Check_Ungrab_Record *next;
|
||||
};
|
||||
|
||||
static int cur_registered = 0;
|
||||
static Check_Ungrab_Record *first_cur = NULL, *last_cur = NULL;
|
||||
|
||||
static void CheckUngrab(Display *dpy, Check_Ungrab_Record *cur)
|
||||
{
|
||||
Window root;
|
||||
int x, y;
|
||||
unsigned w, h, b, d;
|
||||
|
||||
XGetGeometry(dpy, cur->window,
|
||||
&root, &x, &y, &w, &h,
|
||||
&b, &d);
|
||||
if ((cur->x < 0) || (cur->y < 0)
|
||||
|| ((unsigned int)cur->x > w) || ((unsigned int)cur->y > h)) {
|
||||
/* Looks bad, but is it a click in a MrEd window
|
||||
that we could care about? */
|
||||
|
||||
wxWindow *w;
|
||||
w = wxLocationToWindow(cur->x_root, cur->y_root);
|
||||
|
||||
if (w) {
|
||||
/* Looks like we need to ungrab */
|
||||
XUngrabPointer(dpy, 0);
|
||||
XUngrabKeyboard(dpy, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static Bool CheckPred(Display *display, XEvent *e, char *args)
|
||||
{
|
||||
Window window;
|
||||
|
@ -197,28 +235,23 @@ static Bool CheckPred(Display *display, XEvent *e, char *args)
|
|||
/* lastUngrabTime keeps us from checking the same events
|
||||
over and over again. */
|
||||
if (e->xbutton.time > lastUngrabTime) {
|
||||
Window root;
|
||||
int x, y;
|
||||
unsigned w, h, b, d;
|
||||
Check_Ungrab_Record *cur;
|
||||
|
||||
XGetGeometry(XtDisplay(widget), e->xbutton.window,
|
||||
&root, &x, &y, &w, &h,
|
||||
&b, &d);
|
||||
if ((e->xbutton.x < 0) || (e->xbutton.y < 0)
|
||||
|| ((unsigned int)e->xbutton.x > w) || ((unsigned int)e->xbutton.y > h)) {
|
||||
/* Looks bad, but is it a click in a MrEd window
|
||||
that we could care about? */
|
||||
|
||||
wxWindow *w;
|
||||
w = wxLocationToWindow(e->xbutton.x_root, e->xbutton.y_root);
|
||||
|
||||
if (w) {
|
||||
/* Looks like we need to ungrab */
|
||||
XUngrabPointer(XtDisplay(widget), 0);
|
||||
XUngrabKeyboard(XtDisplay(widget), 0);
|
||||
}
|
||||
if (!cur_registered) {
|
||||
wxREGGLOB(first_cur);
|
||||
wxREGGLOB(last_cur);
|
||||
}
|
||||
|
||||
cur = new WXGC_PTRS Check_Ungrab_Record;
|
||||
cur->window = e->xbutton.window;
|
||||
cur->x = e->xbutton.x;
|
||||
cur->y = e->xbutton.y;
|
||||
cur->x_root = e->xbutton.x_root;
|
||||
cur->y_root = e->xbutton.y_root;
|
||||
if (last_cur)
|
||||
last_cur->next = cur;
|
||||
else
|
||||
first_cur = cur;
|
||||
last_cur = cur;
|
||||
lastUngrabTime = e->xbutton.time;
|
||||
}
|
||||
}
|
||||
|
@ -339,6 +372,7 @@ int MrEdGetNextEvent(int check_only, int current_only,
|
|||
XEvent *event, MrEdContext **which)
|
||||
{
|
||||
Display *d;
|
||||
int got;
|
||||
|
||||
if (which)
|
||||
*which = NULL;
|
||||
|
@ -351,7 +385,15 @@ int MrEdGetNextEvent(int check_only, int current_only,
|
|||
else
|
||||
d = XtDisplay(orig_top_level);
|
||||
|
||||
if (XCheckIfEvent(d, event, CheckPred, (char *)which)) {
|
||||
got = XCheckIfEvent(d, event, CheckPred, (char *)which);
|
||||
|
||||
while (first_cur) {
|
||||
CheckUngrab(d, first_cur);
|
||||
first_cur = first_cur->next;
|
||||
}
|
||||
last_cur = NULL;
|
||||
|
||||
if (got) {
|
||||
just_check = 0;
|
||||
return 1;
|
||||
} else if (short_circuit) {
|
||||
|
@ -722,3 +764,179 @@ int wxUTF8StringToChar(char *str, int slen)
|
|||
NULL, 0, '?');
|
||||
return (int)s[0];
|
||||
}
|
||||
|
||||
/***********************************************************************/
|
||||
|
||||
static int has_property(Display *d, Window w, Atom atag)
|
||||
{
|
||||
Atom actual;
|
||||
int format;
|
||||
unsigned long count, remaining;
|
||||
unsigned char *data = 0;
|
||||
|
||||
XGetWindowProperty(d, w, atag,
|
||||
0, 0x8000000L, FALSE,
|
||||
AnyPropertyType, &actual, &format,
|
||||
&count, &remaining, &data);
|
||||
|
||||
if (data)
|
||||
XFree(data);
|
||||
|
||||
return (actual != None);
|
||||
}
|
||||
|
||||
static int wxSendOrSetTag(char *pre_tag, char *tag, char *msg)
|
||||
{
|
||||
Display *d;
|
||||
Window root, parent, *children;
|
||||
unsigned int n, i;
|
||||
Atom atag, apre_tag;
|
||||
Window target = 0, me;
|
||||
int try_again = 0;
|
||||
|
||||
/* Elect a leader, relying on the fact that the X server serializes
|
||||
its interactions.
|
||||
|
||||
Each client sets a pre-tag, and then checks all windows. If any
|
||||
window has a (non-pre) tag already, then that's the leader. If no
|
||||
one else has a pre tag, then this client is elected, and it sets
|
||||
the tag on itself. If someone else has a pre tag, we try again;
|
||||
if the other window id is lower, this client drops it pre tag, so
|
||||
that the other will be elected eventually. Note that if two
|
||||
clients set a pre tag, then one must see the other (because
|
||||
neither looks until its tag is set). Livelock is a possibility if
|
||||
clients continuously appear with ever higher window ids, but that
|
||||
possibility is exceedingly remote. */
|
||||
|
||||
if (!orig_top_level)
|
||||
d = XtDisplay(save_top_level);
|
||||
else
|
||||
d = XtDisplay(orig_top_level);
|
||||
|
||||
apre_tag = XInternAtom(d, pre_tag, False);
|
||||
atag = XInternAtom(d, tag, False);
|
||||
|
||||
wx_single_instance_tag = atag;
|
||||
|
||||
me = wxAddClipboardWindowProperty(apre_tag);
|
||||
|
||||
XFlush(d);
|
||||
|
||||
do {
|
||||
XSync(d, FALSE);
|
||||
if (XQueryTree(d, DefaultRootWindow(d),
|
||||
&root, &parent, &children, &n)) {
|
||||
for (i = n; i--; ) {
|
||||
if (children[i] != me) {
|
||||
if (has_property(d, children[i], atag)) {
|
||||
target = children[i];
|
||||
try_again = 0;
|
||||
break;
|
||||
} else if (has_property(d, children[i], apre_tag)) {
|
||||
if ((long)me >= (long)children[i])
|
||||
XDeleteProperty(d, me, apre_tag);
|
||||
try_again = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (children)
|
||||
XFree(children);
|
||||
}
|
||||
} while (try_again);
|
||||
|
||||
if (target) {
|
||||
GC_CAN_IGNORE XEvent xevent;
|
||||
long mlen, offset = 0;
|
||||
int sent_last = 0;
|
||||
|
||||
mlen = strlen(msg);
|
||||
|
||||
/* Send the message(s): */
|
||||
while (!sent_last) {
|
||||
memset(&xevent, 0, sizeof (xevent));
|
||||
|
||||
xevent.xany.type = ClientMessage;
|
||||
xevent.xany.display = d;
|
||||
xevent.xclient.window = target;
|
||||
xevent.xclient.message_type = atag;
|
||||
xevent.xclient.format = 8;
|
||||
|
||||
{
|
||||
int i = sizeof(Window);
|
||||
long w = (long)me;
|
||||
|
||||
while (i--) {
|
||||
xevent.xclient.data.b[i] = (char)(w & 0xFF);
|
||||
w = w >> 8;
|
||||
}
|
||||
}
|
||||
|
||||
if (offset < mlen) {
|
||||
long amt;
|
||||
amt = mlen - offset;
|
||||
if (amt > (int)(20 - sizeof(Window)))
|
||||
amt = 20 - sizeof(Window);
|
||||
memcpy(xevent.xclient.data.b + sizeof(Window), msg + offset, amt);
|
||||
offset += amt;
|
||||
sent_last = (amt < (int)(20 - sizeof(Window)));
|
||||
} else
|
||||
sent_last = 1;
|
||||
|
||||
XSendEvent(d, target, 0, 0, &xevent);
|
||||
}
|
||||
|
||||
XFlush(d);
|
||||
XSync(d, FALSE);
|
||||
|
||||
return 1;
|
||||
} else {
|
||||
/* Set the property on the clipboard window */
|
||||
wxAddClipboardWindowProperty(atag);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
# define SINGLE_INSTANCE_HANDLER_CODE \
|
||||
"(lambda (f)" \
|
||||
" (let ([path (simplify-path" \
|
||||
" (path->complete-path" \
|
||||
" (or (find-executable-path (find-system-path 'run-file) #f)" \
|
||||
" (find-system-path 'run-file))" \
|
||||
" (current-directory)))])" \
|
||||
" (let ([tag (string->bytes/utf-8" \
|
||||
" (format \"~a_~a\" path (version)))])" \
|
||||
" (f tag " \
|
||||
" (bytes-append #\"pre\" tag)" \
|
||||
" (apply" \
|
||||
" bytes-append" \
|
||||
" (map (lambda (s)" \
|
||||
" (let ([s (path->string" \
|
||||
" (path->complete-path s (current-directory)))])" \
|
||||
" (string->bytes/utf-8" \
|
||||
" (format \"~a:~a\"" \
|
||||
" (string-length s)" \
|
||||
" s))))" \
|
||||
" (vector->list" \
|
||||
" (current-command-line-arguments))))))))"
|
||||
|
||||
static Scheme_Object *prep_single_instance(int argc, Scheme_Object **argv)
|
||||
{
|
||||
return (wxSendOrSetTag(SCHEME_BYTE_STR_VAL(argv[0]),
|
||||
SCHEME_BYTE_STR_VAL(argv[1]),
|
||||
SCHEME_BYTE_STR_VAL(argv[2]))
|
||||
? scheme_true
|
||||
: scheme_false);
|
||||
}
|
||||
|
||||
int wxCheckSingleInstance(Scheme_Env *global_env)
|
||||
{
|
||||
Scheme_Object *a[1], *v;
|
||||
a[0] = scheme_make_prim(prep_single_instance);
|
||||
v = scheme_apply(scheme_eval_string(SINGLE_INSTANCE_HANDLER_CODE,
|
||||
global_env),
|
||||
1,
|
||||
a);
|
||||
return SCHEME_TRUEP(v);
|
||||
}
|
||||
|
|
|
@ -2359,10 +2359,7 @@ static char *win_find_home()
|
|||
static char *x_display_str;
|
||||
extern void wxsRememberDisplay(char *str)
|
||||
{
|
||||
if (str)
|
||||
x_display_str = str;
|
||||
else
|
||||
x_display_str = getenv("DISPLAY");
|
||||
x_display_str = str;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
|
|
@ -53,6 +53,8 @@ int wx_visual_depth;
|
|||
Colormap wx_default_colormap;
|
||||
unsigned long wx_white_pixel, wx_black_pixel;
|
||||
|
||||
int wx_single_instance = 0;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// wxApp implementation
|
||||
//-----------------------------------------------------------------------------
|
||||
|
@ -115,6 +117,8 @@ typedef struct {
|
|||
int arg_count;
|
||||
} X_flag_entry;
|
||||
|
||||
#define SINGLE_INSTANCE "-singleInstance"
|
||||
|
||||
X_flag_entry X_flags[] = {
|
||||
{ "-display", 1 },
|
||||
{ "-geometry", 1 },
|
||||
|
@ -134,6 +138,7 @@ X_flag_entry X_flags[] = {
|
|||
{ "-title", 1 },
|
||||
{ "-xnllanguage", 1 },
|
||||
{ "-xrm", 1 },
|
||||
{ SINGLE_INSTANCE, 0},
|
||||
{ NULL, 0 }
|
||||
};
|
||||
|
||||
|
@ -189,6 +194,9 @@ int wxEntry(int argc, char *argv[])
|
|||
xargc = filter_x_readable(argv, argc, &x_display_str);
|
||||
ate = xargc - 1;
|
||||
|
||||
if (!x_display_str)
|
||||
x_display_str = getenv("DISPLAY");
|
||||
|
||||
/* Remember -display or DISPLAY, in case someone needs it: */
|
||||
wxsRememberDisplay(x_display_str);
|
||||
|
||||
|
@ -199,8 +207,6 @@ int wxEntry(int argc, char *argv[])
|
|||
&xargc, argv); // command line arguments
|
||||
|
||||
if (!wxAPP_DISPLAY) {
|
||||
if (!x_display_str)
|
||||
x_display_str = getenv("DISPLAY");
|
||||
if (!x_display_str) {
|
||||
printf("DISPLAY environment variable not set and no -display argument\n");
|
||||
} else {
|
||||
|
@ -209,6 +215,15 @@ int wxEntry(int argc, char *argv[])
|
|||
exit(1);
|
||||
}
|
||||
|
||||
|
||||
if ((xargc > 1) && !strcmp("-singleInstance", argv[1])) {
|
||||
wx_single_instance = 1;
|
||||
--xargc;
|
||||
if (xargc > 1) {
|
||||
argv[1] = argv[2];
|
||||
}
|
||||
}
|
||||
|
||||
if (xargc != 1) {
|
||||
printf("%s: standard X Window System flag \"%s\" was rejected\n",
|
||||
argv[0], argv[1]);
|
||||
|
@ -236,7 +251,8 @@ int wxEntry(int argc, char *argv[])
|
|||
wxAPP_VISUAL = vi2.visual;
|
||||
wx_visual_depth = 24;
|
||||
wx_default_colormap = XCreateColormap(wxAPP_DISPLAY,
|
||||
RootWindow(wxAPP_DISPLAY, DefaultScreen(wxAPP_DISPLAY)),
|
||||
RootWindow(wxAPP_DISPLAY,
|
||||
DefaultScreen(wxAPP_DISPLAY)),
|
||||
wxAPP_VISUAL,
|
||||
AllocNone);
|
||||
|
||||
|
|
|
@ -108,6 +108,14 @@ void wxInitClipboard(void)
|
|||
xa_clipboard = ATOM("CLIPBOARD");
|
||||
}
|
||||
|
||||
Window wxAddClipboardWindowProperty(Atom prop)
|
||||
{
|
||||
unsigned char data[1] = { 'm' };
|
||||
XChangeProperty(XtDisplay(wx_clipWindow), XtWindow(wx_clipWindow),
|
||||
prop, prop, 8, PropModeReplace, data, 1);
|
||||
return XtWindow(wx_clipWindow);
|
||||
}
|
||||
|
||||
static void AddClipboardFrame(wxClipboard *cb, int on)
|
||||
{
|
||||
if (!on)
|
||||
|
|
|
@ -74,10 +74,24 @@ static Bool grabbing_panel_regsitered;
|
|||
static int dnd_inited = 0;
|
||||
static DndClass dnd;
|
||||
|
||||
Atom wx_single_instance_tag = 0;
|
||||
|
||||
#ifndef NO_XMB_LOOKUP_STRING
|
||||
static XIM the_im;
|
||||
#endif
|
||||
|
||||
class Accum_Single_Instance_Message {
|
||||
public:
|
||||
long src;
|
||||
char *accum;
|
||||
int len, size;
|
||||
Accum_Single_Instance_Message *next;
|
||||
};
|
||||
static int si_registered;
|
||||
static Accum_Single_Instance_Message *si_msgs;
|
||||
static void parse_and_drop_runtime(int len, char *s);
|
||||
extern void wxDrop_Runtime(char **argv, int argc);
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// wxWindow constructor
|
||||
//-----------------------------------------------------------------------------
|
||||
|
@ -1408,6 +1422,75 @@ void wxWindow::FrameEventHandler(Widget w,
|
|||
if (win->OnClose())
|
||||
win->Show(FALSE);
|
||||
}
|
||||
if (wx_single_instance_tag) {
|
||||
if (xev->xclient.message_type == wx_single_instance_tag) {
|
||||
/* Accumulate msg data */
|
||||
long src = 0;
|
||||
int i;
|
||||
Accum_Single_Instance_Message *msg, *prev = NULL;
|
||||
|
||||
if (!si_registered) {
|
||||
wxREGGLOB(si_msgs);
|
||||
}
|
||||
|
||||
for (i = sizeof(Window); i--; ) {
|
||||
src = (src << 8) | ((int)xev->xclient.data.b[i]);
|
||||
}
|
||||
|
||||
for (msg = si_msgs; msg; msg = msg->next) {
|
||||
if (msg->src == src)
|
||||
break;
|
||||
}
|
||||
if (!msg) {
|
||||
char *s;
|
||||
s = new WXGC_ATOMIC char[128];
|
||||
msg = new WXGC_PTRS Accum_Single_Instance_Message;
|
||||
msg->next = si_msgs;
|
||||
si_msgs = msg;
|
||||
msg->src = src;
|
||||
msg->accum = s;
|
||||
msg->len = 0;
|
||||
msg->size = 128;
|
||||
}
|
||||
|
||||
{
|
||||
int len = sizeof(Window);
|
||||
while (len < 20 && xev->xclient.data.b[len]) {
|
||||
len++;
|
||||
}
|
||||
len -= sizeof(Window);
|
||||
|
||||
if (len) {
|
||||
/* accumulate data */
|
||||
if (msg->size < msg->len + 1 + len) {
|
||||
char *naya;
|
||||
int new_size = msg->size * 2;
|
||||
naya = new WXGC_ATOMIC char[new_size];
|
||||
memcpy(naya, msg->accum, msg->len);
|
||||
msg->accum = naya;
|
||||
msg->size = new_size;
|
||||
}
|
||||
memcpy(msg->accum + msg->len,
|
||||
xev->xclient.data.b + sizeof(Window),
|
||||
len);
|
||||
msg->len += len;
|
||||
if (len < (int)(20 - sizeof(Window)))
|
||||
len = 0; /* inidicate that we're done */
|
||||
}
|
||||
|
||||
if (!len) {
|
||||
/* done */
|
||||
if (prev)
|
||||
prev->next = msg->next;
|
||||
else
|
||||
si_msgs = msg->next;
|
||||
msg->accum[msg->len] = 0;
|
||||
|
||||
parse_and_drop_runtime(msg->len, msg->accum);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (dnd_inited) {
|
||||
if (xev->xclient.message_type == dnd.XdndEnter) {
|
||||
/* Ok... */
|
||||
|
@ -2413,3 +2496,49 @@ wxWindow *wxWindow::FindChildByWidget(Widget w)
|
|||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void parse_and_drop_runtime(int len, char *s)
|
||||
{
|
||||
char **argv, *a;
|
||||
int cnt = 0, pos = 0;
|
||||
int sz;
|
||||
|
||||
while (pos < len) {
|
||||
sz = 0;
|
||||
while ((pos < len) && (s[pos] != ':')) {
|
||||
sz = (sz * 10) + (s[pos] - '0');
|
||||
pos++;
|
||||
}
|
||||
pos++;
|
||||
if (sz > 0)
|
||||
pos += sz;
|
||||
cnt++;
|
||||
}
|
||||
|
||||
argv = new WXGC_PTRS char*[cnt];
|
||||
|
||||
pos = cnt = 0;
|
||||
while (pos < len) {
|
||||
sz = 0;
|
||||
while ((pos < len) && (s[pos] != ':')) {
|
||||
sz = (sz * 10) + (s[pos] - '0');
|
||||
pos++;
|
||||
}
|
||||
pos++;
|
||||
|
||||
if (sz > len - pos)
|
||||
sz = len - pos;
|
||||
if (sz < 0)
|
||||
sz = 0;
|
||||
a = new WXGC_ATOMIC char[sz + 1];
|
||||
memcpy(a, s + pos, sz);
|
||||
a[sz] = 0;
|
||||
argv[cnt] = a;
|
||||
|
||||
if (sz > 0)
|
||||
pos += sz;
|
||||
cnt++;
|
||||
}
|
||||
|
||||
wxDrop_Runtime(argv, cnt);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user