mirror of
https://github.com/tmux/tmux.git
synced 2025-01-07 16:28:48 +00:00
Merge branch 'obsd-master'
This commit is contained in:
commit
d4177e954c
5
cfg.c
5
cfg.c
@ -301,9 +301,10 @@ load_cfg(const char *path, struct client *c, struct cmdq_item *item, int quiet)
|
|||||||
free(buf);
|
free(buf);
|
||||||
|
|
||||||
new_item = cmdq_get_command(cmdlist, NULL, NULL, 0);
|
new_item = cmdq_get_command(cmdlist, NULL, NULL, 0);
|
||||||
if (item != NULL)
|
if (item != NULL) {
|
||||||
cmdq_insert_after(item, new_item);
|
cmdq_insert_after(item, new_item);
|
||||||
else
|
item = new_item;
|
||||||
|
} else
|
||||||
cmdq_append(c, new_item);
|
cmdq_append(c, new_item);
|
||||||
cmd_list_free(cmdlist);
|
cmd_list_free(cmdlist);
|
||||||
|
|
||||||
|
@ -39,8 +39,8 @@ const struct cmd_entry cmd_display_message_entry = {
|
|||||||
.name = "display-message",
|
.name = "display-message",
|
||||||
.alias = "display",
|
.alias = "display",
|
||||||
|
|
||||||
.args = { "ac:pt:F:v", 0, 1 },
|
.args = { "ac:Ipt:F:v", 0, 1 },
|
||||||
.usage = "[-apv] [-c target-client] [-F format] "
|
.usage = "[-aIpv] [-c target-client] [-F format] "
|
||||||
CMD_TARGET_PANE_USAGE " [message]",
|
CMD_TARGET_PANE_USAGE " [message]",
|
||||||
|
|
||||||
.target = { 't', CMD_FIND_PANE, 0 },
|
.target = { 't', CMD_FIND_PANE, 0 },
|
||||||
@ -66,10 +66,19 @@ cmd_display_message_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
struct winlink *wl = item->target.wl;
|
struct winlink *wl = item->target.wl;
|
||||||
struct window_pane *wp = item->target.wp;
|
struct window_pane *wp = item->target.wp;
|
||||||
const char *template;
|
const char *template;
|
||||||
char *msg;
|
char *msg, *cause;
|
||||||
struct format_tree *ft;
|
struct format_tree *ft;
|
||||||
int flags;
|
int flags;
|
||||||
|
|
||||||
|
if (args_has(args, 'I')) {
|
||||||
|
if (window_pane_start_input(wp, item, &cause) != 0) {
|
||||||
|
cmdq_error(item, "%s", cause);
|
||||||
|
free(cause);
|
||||||
|
return (CMD_RETURN_ERROR);
|
||||||
|
}
|
||||||
|
return (CMD_RETURN_WAIT);
|
||||||
|
}
|
||||||
|
|
||||||
if (args_has(args, 'F') && args->argc != 0) {
|
if (args_has(args, 'F') && args->argc != 0) {
|
||||||
cmdq_error(item, "only one of -F or argument must be given");
|
cmdq_error(item, "only one of -F or argument must be given");
|
||||||
return (CMD_RETURN_ERROR);
|
return (CMD_RETURN_ERROR);
|
||||||
|
@ -39,9 +39,10 @@ const struct cmd_entry cmd_split_window_entry = {
|
|||||||
.name = "split-window",
|
.name = "split-window",
|
||||||
.alias = "splitw",
|
.alias = "splitw",
|
||||||
|
|
||||||
.args = { "bc:de:fF:l:hp:Pt:v", 0, -1 },
|
.args = { "bc:de:fF:hIl:p:Pt:v", 0, -1 },
|
||||||
.usage = "[-bdefhvP] [-c start-directory] [-e environment] [-F format] "
|
.usage = "[-bdefhIPv] [-c start-directory] [-e environment] "
|
||||||
"[-p percentage|-l size] " CMD_TARGET_PANE_USAGE " [command]",
|
"[-F format] [-p percentage|-l size] " CMD_TARGET_PANE_USAGE
|
||||||
|
" [command]",
|
||||||
|
|
||||||
.target = { 't', CMD_FIND_PANE, 0 },
|
.target = { 't', CMD_FIND_PANE, 0 },
|
||||||
|
|
||||||
@ -62,7 +63,7 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
enum layout_type type;
|
enum layout_type type;
|
||||||
struct layout_cell *lc;
|
struct layout_cell *lc;
|
||||||
struct cmd_find_state fs;
|
struct cmd_find_state fs;
|
||||||
int size, percentage, flags;
|
int size, percentage, flags, input;
|
||||||
const char *template, *add;
|
const char *template, *add;
|
||||||
char *cause, *cp;
|
char *cause, *cp;
|
||||||
struct args_value *value;
|
struct args_value *value;
|
||||||
@ -93,12 +94,15 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
size = -1;
|
size = -1;
|
||||||
|
|
||||||
server_unzoom_window(wp->window);
|
server_unzoom_window(wp->window);
|
||||||
|
input = (args_has(args, 'I') && args->argc == 0);
|
||||||
|
|
||||||
flags = 0;
|
flags = 0;
|
||||||
if (args_has(args, 'b'))
|
if (args_has(args, 'b'))
|
||||||
flags |= SPAWN_BEFORE;
|
flags |= SPAWN_BEFORE;
|
||||||
if (args_has(args, 'f'))
|
if (args_has(args, 'f'))
|
||||||
flags |= SPAWN_FULLSIZE;
|
flags |= SPAWN_FULLSIZE;
|
||||||
|
if (input || (args->argc == 1 && *args->argv[0] == '\0'))
|
||||||
|
flags |= SPAWN_EMPTY;
|
||||||
|
|
||||||
lc = layout_split_pane(wp, type, size, flags);
|
lc = layout_split_pane(wp, type, size, flags);
|
||||||
if (lc == NULL) {
|
if (lc == NULL) {
|
||||||
@ -133,10 +137,18 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
sc.flags |= SPAWN_DETACHED;
|
sc.flags |= SPAWN_DETACHED;
|
||||||
|
|
||||||
if ((new_wp = spawn_pane(&sc, &cause)) == NULL) {
|
if ((new_wp = spawn_pane(&sc, &cause)) == NULL) {
|
||||||
|
layout_close_pane(new_wp);
|
||||||
cmdq_error(item, "create pane failed: %s", cause);
|
cmdq_error(item, "create pane failed: %s", cause);
|
||||||
free(cause);
|
free(cause);
|
||||||
return (CMD_RETURN_ERROR);
|
return (CMD_RETURN_ERROR);
|
||||||
}
|
}
|
||||||
|
if (input && window_pane_start_input(new_wp, item, &cause) != 0) {
|
||||||
|
layout_close_pane(new_wp);
|
||||||
|
window_remove_pane(wp->window, new_wp);
|
||||||
|
cmdq_error(item, "%s", cause);
|
||||||
|
free(cause);
|
||||||
|
return (CMD_RETURN_ERROR);
|
||||||
|
}
|
||||||
if (!args_has(args, 'd'))
|
if (!args_has(args, 'd'))
|
||||||
cmd_find_from_winlink_pane(current, wl, new_wp, 0);
|
cmd_find_from_winlink_pane(current, wl, new_wp, 0);
|
||||||
server_redraw_window(wp->window);
|
server_redraw_window(wp->window);
|
||||||
@ -154,5 +166,7 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
cmdq_insert_hook(s, item, &fs, "after-split-window");
|
cmdq_insert_hook(s, item, &fs, "after-split-window");
|
||||||
|
|
||||||
environ_free(sc.environ);
|
environ_free(sc.environ);
|
||||||
|
if (input)
|
||||||
|
return (CMD_RETURN_WAIT);
|
||||||
return (CMD_RETURN_NORMAL);
|
return (CMD_RETURN_NORMAL);
|
||||||
}
|
}
|
||||||
|
5
format.c
5
format.c
@ -2047,7 +2047,10 @@ format_defaults_pane(struct format_tree *ft, struct window_pane *wp)
|
|||||||
|
|
||||||
if ((wp->flags & PANE_STATUSREADY) && WIFEXITED(status))
|
if ((wp->flags & PANE_STATUSREADY) && WIFEXITED(status))
|
||||||
format_add(ft, "pane_dead_status", "%d", WEXITSTATUS(status));
|
format_add(ft, "pane_dead_status", "%d", WEXITSTATUS(status));
|
||||||
format_add(ft, "pane_dead", "%d", wp->fd == -1);
|
if (~wp->flags & PANE_EMPTY)
|
||||||
|
format_add(ft, "pane_dead", "%d", wp->fd == -1);
|
||||||
|
else
|
||||||
|
format_add(ft, "pane_dead", "0");
|
||||||
|
|
||||||
format_add(ft, "pane_left", "%u", wp->xoff);
|
format_add(ft, "pane_left", "%u", wp->xoff);
|
||||||
format_add(ft, "pane_top", "%u", wp->yoff);
|
format_add(ft, "pane_top", "%u", wp->yoff);
|
||||||
|
2
input.c
2
input.c
@ -1185,6 +1185,8 @@ input_c0_dispatch(struct input_ctx *ictx)
|
|||||||
case '\013': /* VT */
|
case '\013': /* VT */
|
||||||
case '\014': /* FF */
|
case '\014': /* FF */
|
||||||
screen_write_linefeed(sctx, 0, ictx->cell.cell.bg);
|
screen_write_linefeed(sctx, 0, ictx->cell.cell.bg);
|
||||||
|
if (s->mode & MODE_CRLF)
|
||||||
|
screen_write_carriagereturn(sctx);
|
||||||
break;
|
break;
|
||||||
case '\015': /* CR */
|
case '\015': /* CR */
|
||||||
screen_write_carriagereturn(sctx);
|
screen_write_carriagereturn(sctx);
|
||||||
|
@ -1681,8 +1681,7 @@ server_client_dispatch(struct imsg *imsg, void *arg)
|
|||||||
evbuffer_add(c->stdin_data, stdindata.data,
|
evbuffer_add(c->stdin_data, stdindata.data,
|
||||||
stdindata.size);
|
stdindata.size);
|
||||||
}
|
}
|
||||||
c->stdin_callback(c, c->stdin_closed,
|
c->stdin_callback(c, c->stdin_closed, c->stdin_callback_data);
|
||||||
c->stdin_callback_data);
|
|
||||||
break;
|
break;
|
||||||
case MSG_RESIZE:
|
case MSG_RESIZE:
|
||||||
if (datalen != 0)
|
if (datalen != 0)
|
||||||
|
@ -440,7 +440,6 @@ server_check_unattached(void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Set stdin callback. */
|
|
||||||
int
|
int
|
||||||
server_set_stdin_callback(struct client *c, void (*cb)(struct client *, int,
|
server_set_stdin_callback(struct client *c, void (*cb)(struct client *, int,
|
||||||
void *), void *cb_data, char **cause)
|
void *), void *cb_data, char **cause)
|
||||||
@ -454,7 +453,7 @@ server_set_stdin_callback(struct client *c, void (*cb)(struct client *, int,
|
|||||||
return (-1);
|
return (-1);
|
||||||
}
|
}
|
||||||
if (c->stdin_callback != NULL) {
|
if (c->stdin_callback != NULL) {
|
||||||
*cause = xstrdup("stdin in use");
|
*cause = xstrdup("stdin is in use");
|
||||||
return (-1);
|
return (-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
48
spawn.c
48
spawn.c
@ -330,6 +330,14 @@ spawn_pane(struct spawn_context *sc, char **cause)
|
|||||||
cmd_log_argv(new_wp->argc, new_wp->argv, __func__);
|
cmd_log_argv(new_wp->argc, new_wp->argv, __func__);
|
||||||
environ_log(child, "%s: environment ", __func__);
|
environ_log(child, "%s: environment ", __func__);
|
||||||
|
|
||||||
|
/* If the command is empty, don't fork a child process. */
|
||||||
|
if (sc->flags & SPAWN_EMPTY) {
|
||||||
|
new_wp->flags |= PANE_EMPTY;
|
||||||
|
new_wp->base.mode &= ~MODE_CURSOR;
|
||||||
|
new_wp->base.mode |= MODE_CRLF;
|
||||||
|
goto complete;
|
||||||
|
}
|
||||||
|
|
||||||
/* Initialize the window size. */
|
/* Initialize the window size. */
|
||||||
memset(&ws, 0, sizeof ws);
|
memset(&ws, 0, sizeof ws);
|
||||||
ws.ws_col = screen_size_x(&new_wp->base);
|
ws.ws_col = screen_size_x(&new_wp->base);
|
||||||
@ -353,25 +361,8 @@ spawn_pane(struct spawn_context *sc, char **cause)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* In the parent process, everything is done now. */
|
/* In the parent process, everything is done now. */
|
||||||
if (new_wp->pid != 0) {
|
if (new_wp->pid != 0)
|
||||||
new_wp->pipe_off = 0;
|
goto complete;
|
||||||
new_wp->flags &= ~PANE_EXITED;
|
|
||||||
|
|
||||||
sigprocmask(SIG_SETMASK, &oldset, NULL);
|
|
||||||
window_pane_set_event(new_wp);
|
|
||||||
|
|
||||||
if (sc->flags & SPAWN_RESPAWN)
|
|
||||||
return (new_wp);
|
|
||||||
if ((~sc->flags & SPAWN_DETACHED) || w->active == NULL) {
|
|
||||||
if (sc->flags & SPAWN_NONOTIFY)
|
|
||||||
window_set_active_pane(w, new_wp, 0);
|
|
||||||
else
|
|
||||||
window_set_active_pane(w, new_wp, 1);
|
|
||||||
}
|
|
||||||
if (~sc->flags & SPAWN_NONOTIFY)
|
|
||||||
notify_window("window-layout-changed", w);
|
|
||||||
return (new_wp);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Child process. Change to the working directory or home if that
|
* Child process. Change to the working directory or home if that
|
||||||
@ -431,4 +422,23 @@ spawn_pane(struct spawn_context *sc, char **cause)
|
|||||||
xasprintf(&argv0, "-%s", new_wp->shell);
|
xasprintf(&argv0, "-%s", new_wp->shell);
|
||||||
execl(new_wp->shell, argv0, (char *)NULL);
|
execl(new_wp->shell, argv0, (char *)NULL);
|
||||||
_exit(1);
|
_exit(1);
|
||||||
|
|
||||||
|
complete:
|
||||||
|
new_wp->pipe_off = 0;
|
||||||
|
new_wp->flags &= ~PANE_EXITED;
|
||||||
|
|
||||||
|
sigprocmask(SIG_SETMASK, &oldset, NULL);
|
||||||
|
window_pane_set_event(new_wp);
|
||||||
|
|
||||||
|
if (sc->flags & SPAWN_RESPAWN)
|
||||||
|
return (new_wp);
|
||||||
|
if ((~sc->flags & SPAWN_DETACHED) || w->active == NULL) {
|
||||||
|
if (sc->flags & SPAWN_NONOTIFY)
|
||||||
|
window_set_active_pane(w, new_wp, 0);
|
||||||
|
else
|
||||||
|
window_set_active_pane(w, new_wp, 1);
|
||||||
|
}
|
||||||
|
if (~sc->flags & SPAWN_NONOTIFY)
|
||||||
|
notify_window("window-layout-changed", w);
|
||||||
|
return (new_wp);
|
||||||
}
|
}
|
||||||
|
26
tmux.1
26
tmux.1
@ -2209,7 +2209,7 @@ is given and the selected window is already the current window,
|
|||||||
the command behaves like
|
the command behaves like
|
||||||
.Ic last-window .
|
.Ic last-window .
|
||||||
.It Xo Ic split-window
|
.It Xo Ic split-window
|
||||||
.Op Fl bdfhvP
|
.Op Fl bdfhIvP
|
||||||
.Op Fl c Ar start-directory
|
.Op Fl c Ar start-directory
|
||||||
.Op Fl e Ar environment
|
.Op Fl e Ar environment
|
||||||
.Oo Fl l
|
.Oo Fl l
|
||||||
@ -2245,6 +2245,24 @@ option creates a new pane spanning the full window height (with
|
|||||||
or full window width (with
|
or full window width (with
|
||||||
.Fl v ) ,
|
.Fl v ) ,
|
||||||
instead of splitting the active pane.
|
instead of splitting the active pane.
|
||||||
|
.Pp
|
||||||
|
An empty
|
||||||
|
.Ar shell-command
|
||||||
|
('') will create a pane with no command running in it.
|
||||||
|
Output can be sent to such a pane with the
|
||||||
|
.Ic display-message
|
||||||
|
command.
|
||||||
|
The
|
||||||
|
.Fl I
|
||||||
|
flag (if
|
||||||
|
.Ar shell-command
|
||||||
|
is not specified or empty)
|
||||||
|
will create an empty pane and forward any output from stdin to it.
|
||||||
|
For example:
|
||||||
|
.Bd -literal -offset indent
|
||||||
|
$ make 2>&1|tmux splitw -dI &
|
||||||
|
.Ed
|
||||||
|
.Pp
|
||||||
All other options have the same meaning as for the
|
All other options have the same meaning as for the
|
||||||
.Ic new-window
|
.Ic new-window
|
||||||
command.
|
command.
|
||||||
@ -4435,7 +4453,7 @@ option.
|
|||||||
This command works only from inside
|
This command works only from inside
|
||||||
.Nm .
|
.Nm .
|
||||||
.It Xo Ic display-message
|
.It Xo Ic display-message
|
||||||
.Op Fl apv
|
.Op Fl aIpv
|
||||||
.Op Fl c Ar target-client
|
.Op Fl c Ar target-client
|
||||||
.Op Fl t Ar target-pane
|
.Op Fl t Ar target-pane
|
||||||
.Op Ar message
|
.Op Ar message
|
||||||
@ -4462,6 +4480,10 @@ is given, otherwise the active pane for the session attached to
|
|||||||
prints verbose logging as the format is parsed and
|
prints verbose logging as the format is parsed and
|
||||||
.Fl a
|
.Fl a
|
||||||
lists the format variables and their values.
|
lists the format variables and their values.
|
||||||
|
.Pp
|
||||||
|
.Fl I
|
||||||
|
forwards any input read from stdin to the empty pane given by
|
||||||
|
.Ar target-pane .
|
||||||
.El
|
.El
|
||||||
.Sh BUFFERS
|
.Sh BUFFERS
|
||||||
.Nm
|
.Nm
|
||||||
|
5
tmux.h
5
tmux.h
@ -524,6 +524,7 @@ struct msg_stderr_data {
|
|||||||
#define MODE_FOCUSON 0x800
|
#define MODE_FOCUSON 0x800
|
||||||
#define MODE_MOUSE_ALL 0x1000
|
#define MODE_MOUSE_ALL 0x1000
|
||||||
#define MODE_ORIGIN 0x2000
|
#define MODE_ORIGIN 0x2000
|
||||||
|
#define MODE_CRLF 0x4000
|
||||||
|
|
||||||
#define ALL_MODES 0xffffff
|
#define ALL_MODES 0xffffff
|
||||||
#define ALL_MOUSE_MODES (MODE_MOUSE_STANDARD|MODE_MOUSE_BUTTON|MODE_MOUSE_ALL)
|
#define ALL_MOUSE_MODES (MODE_MOUSE_STANDARD|MODE_MOUSE_BUTTON|MODE_MOUSE_ALL)
|
||||||
@ -804,6 +805,7 @@ struct window_pane {
|
|||||||
#define PANE_EXITED 0x100
|
#define PANE_EXITED 0x100
|
||||||
#define PANE_STATUSREADY 0x200
|
#define PANE_STATUSREADY 0x200
|
||||||
#define PANE_STATUSDRAWN 0x400
|
#define PANE_STATUSDRAWN 0x400
|
||||||
|
#define PANE_EMPTY 0x800
|
||||||
|
|
||||||
int argc;
|
int argc;
|
||||||
char **argv;
|
char **argv;
|
||||||
@ -1600,6 +1602,7 @@ struct spawn_context {
|
|||||||
#define SPAWN_BEFORE 0x8
|
#define SPAWN_BEFORE 0x8
|
||||||
#define SPAWN_NONOTIFY 0x10
|
#define SPAWN_NONOTIFY 0x10
|
||||||
#define SPAWN_FULLSIZE 0x20
|
#define SPAWN_FULLSIZE 0x20
|
||||||
|
#define SPAWN_EMPTY 0x40
|
||||||
};
|
};
|
||||||
|
|
||||||
/* tmux.c */
|
/* tmux.c */
|
||||||
@ -2319,6 +2322,8 @@ void window_add_ref(struct window *, const char *);
|
|||||||
void window_remove_ref(struct window *, const char *);
|
void window_remove_ref(struct window *, const char *);
|
||||||
void winlink_clear_flags(struct winlink *);
|
void winlink_clear_flags(struct winlink *);
|
||||||
int winlink_shuffle_up(struct session *, struct winlink *);
|
int winlink_shuffle_up(struct session *, struct winlink *);
|
||||||
|
int window_pane_start_input(struct window_pane *,
|
||||||
|
struct cmdq_item *, char **);
|
||||||
|
|
||||||
/* layout.c */
|
/* layout.c */
|
||||||
u_int layout_count_cells(struct layout_cell *);
|
u_int layout_count_cells(struct layout_cell *);
|
||||||
|
47
window.c
47
window.c
@ -70,6 +70,11 @@ const struct window_mode *all_window_modes[] = {
|
|||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct window_pane_input_data {
|
||||||
|
struct cmdq_item *item;
|
||||||
|
u_int wp;
|
||||||
|
};
|
||||||
|
|
||||||
static struct window_pane *window_pane_create(struct window *, u_int, u_int,
|
static struct window_pane *window_pane_create(struct window *, u_int, u_int,
|
||||||
u_int);
|
u_int);
|
||||||
static void window_pane_destroy(struct window_pane *);
|
static void window_pane_destroy(struct window_pane *);
|
||||||
@ -1467,3 +1472,45 @@ winlink_shuffle_up(struct session *s, struct winlink *wl)
|
|||||||
|
|
||||||
return (idx);
|
return (idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
window_pane_input_callback(struct client *c, int closed, void *data)
|
||||||
|
{
|
||||||
|
struct window_pane_input_data *cdata = data;
|
||||||
|
struct window_pane *wp;
|
||||||
|
|
||||||
|
wp = window_pane_find_by_id(cdata->wp);
|
||||||
|
if (wp == NULL || closed || c->flags & CLIENT_DEAD) {
|
||||||
|
c->stdin_callback = NULL;
|
||||||
|
server_client_unref(c);
|
||||||
|
|
||||||
|
cdata->item->flags &= ~CMDQ_WAITING;
|
||||||
|
free(cdata);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (evbuffer_add_buffer(wp->event->input, c->stdin_data) != 0)
|
||||||
|
evbuffer_drain(c->stdin_data, EVBUFFER_LENGTH(c->stdin_data));
|
||||||
|
input_parse(wp);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
window_pane_start_input(struct window_pane *wp, struct cmdq_item *item,
|
||||||
|
char **cause)
|
||||||
|
{
|
||||||
|
struct client *c = item->client;
|
||||||
|
struct window_pane_input_data *cdata;
|
||||||
|
|
||||||
|
if (~wp->flags & PANE_EMPTY) {
|
||||||
|
*cause = xstrdup("pane is not empty");
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
cdata = xmalloc(sizeof *cdata);
|
||||||
|
cdata->item = item;
|
||||||
|
cdata->wp = wp->id;
|
||||||
|
|
||||||
|
return (server_set_stdin_callback(c, window_pane_input_callback, cdata,
|
||||||
|
cause));
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user