mirror of
https://github.com/tmux/tmux.git
synced 2024-12-04 19:58:48 +00:00
Conflicts:
Makefile
This commit is contained in:
commit
5862f59ed7
@ -118,6 +118,7 @@ dist_tmux_SOURCES = \
|
||||
cmd-send-keys.c \
|
||||
cmd-set-buffer.c \
|
||||
cmd-set-environment.c \
|
||||
cmd-set-hook.c \
|
||||
cmd-set-option.c \
|
||||
cmd-show-environment.c \
|
||||
cmd-show-messages.c \
|
||||
@ -138,6 +139,7 @@ dist_tmux_SOURCES = \
|
||||
format.c \
|
||||
grid-view.c \
|
||||
grid.c \
|
||||
hooks.c \
|
||||
input-keys.c \
|
||||
input.c \
|
||||
job.c \
|
||||
|
@ -108,7 +108,7 @@ cmd_attach_session(struct cmd_q *cmdq, const char *tflag, int dflag, int rflag,
|
||||
TAILQ_FOREACH(c_loop, &clients, entry) {
|
||||
if (c_loop->session != s || c == c_loop)
|
||||
continue;
|
||||
proc_send_s(c_loop->peer, MSG_DETACH, s->name);
|
||||
server_client_detach(c, MSG_DETACH);
|
||||
}
|
||||
}
|
||||
|
||||
@ -139,7 +139,7 @@ cmd_attach_session(struct cmd_q *cmdq, const char *tflag, int dflag, int rflag,
|
||||
TAILQ_FOREACH(c_loop, &clients, entry) {
|
||||
if (c_loop->session != s || c == c_loop)
|
||||
continue;
|
||||
proc_send_s(c_loop->peer, MSG_DETACH, s->name);
|
||||
server_client_detach(c_loop, MSG_DETACH);
|
||||
}
|
||||
}
|
||||
|
||||
@ -159,6 +159,7 @@ cmd_attach_session(struct cmd_q *cmdq, const char *tflag, int dflag, int rflag,
|
||||
|
||||
if (~c->flags & CLIENT_CONTROL)
|
||||
proc_send(c->peer, MSG_READY, -1, NULL, 0);
|
||||
hooks_run(c->session->hooks, "client-attached", c);
|
||||
cmdq->client_exit = 0;
|
||||
}
|
||||
recalculate_sizes();
|
||||
|
@ -72,9 +72,8 @@ cmd_detach_client_exec(struct cmd *self, struct cmd_q *cmdq)
|
||||
return (CMD_RETURN_ERROR);
|
||||
|
||||
TAILQ_FOREACH(cloop, &clients, entry) {
|
||||
if (cloop->session != s)
|
||||
continue;
|
||||
proc_send_s(cloop->peer, msgtype, cloop->session->name);
|
||||
if (cloop->session == s)
|
||||
server_client_detach(cloop, msgtype);
|
||||
}
|
||||
return (CMD_RETURN_STOP);
|
||||
}
|
||||
@ -85,13 +84,12 @@ cmd_detach_client_exec(struct cmd *self, struct cmd_q *cmdq)
|
||||
|
||||
if (args_has(args, 'a')) {
|
||||
TAILQ_FOREACH(cloop, &clients, entry) {
|
||||
if (cloop->session == NULL || cloop == c)
|
||||
continue;
|
||||
proc_send_s(cloop->peer, msgtype, cloop->session->name);
|
||||
if (cloop->session != NULL && cloop != c)
|
||||
server_client_detach(cloop, msgtype);
|
||||
}
|
||||
return (CMD_RETURN_NORMAL);
|
||||
}
|
||||
|
||||
proc_send_s(c->peer, msgtype, c->session->name);
|
||||
server_client_detach(c, msgtype);
|
||||
return (CMD_RETURN_STOP);
|
||||
}
|
||||
|
116
cmd-set-hook.c
Normal file
116
cmd-set-hook.c
Normal file
@ -0,0 +1,116 @@
|
||||
/* $OpenBSD$ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2012 Thomas Adam <thomas@xteddy.org>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
|
||||
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "tmux.h"
|
||||
|
||||
/*
|
||||
* Set or show global or session hooks.
|
||||
*/
|
||||
|
||||
enum cmd_retval cmd_set_hook_exec(struct cmd *, struct cmd_q *);
|
||||
|
||||
const struct cmd_entry cmd_set_hook_entry = {
|
||||
"set-hook", NULL,
|
||||
"gt:u", 1, 2,
|
||||
"[-gu] " CMD_TARGET_SESSION_USAGE " hook-name [command]",
|
||||
0,
|
||||
cmd_set_hook_exec
|
||||
};
|
||||
|
||||
const struct cmd_entry cmd_show_hooks_entry = {
|
||||
"show-hooks", NULL,
|
||||
"gt:", 0, 1,
|
||||
"[-g] " CMD_TARGET_SESSION_USAGE,
|
||||
0,
|
||||
cmd_set_hook_exec
|
||||
};
|
||||
|
||||
enum cmd_retval
|
||||
cmd_set_hook_exec(struct cmd *self, struct cmd_q *cmdq)
|
||||
{
|
||||
struct args *args = self->args;
|
||||
struct session *s;
|
||||
struct cmd_list *cmdlist;
|
||||
struct hooks *hooks;
|
||||
struct hook *hook;
|
||||
char *cause, *tmp;
|
||||
const char *name, *cmd;
|
||||
|
||||
if (args_has(args, 'g'))
|
||||
hooks = global_hooks;
|
||||
else {
|
||||
s = cmd_find_session(cmdq, args_get(args, 't'), 0);
|
||||
if (s == NULL)
|
||||
return (CMD_RETURN_ERROR);
|
||||
hooks = s->hooks;
|
||||
}
|
||||
|
||||
if (self->entry == &cmd_show_hooks_entry) {
|
||||
hook = hooks_first(hooks);
|
||||
while (hook != NULL) {
|
||||
tmp = cmd_list_print(hook->cmdlist);
|
||||
cmdq_print(cmdq, "%s -> %s", hook->name, tmp);
|
||||
free(tmp);
|
||||
|
||||
hook = hooks_next(hook);
|
||||
}
|
||||
return (CMD_RETURN_NORMAL);
|
||||
}
|
||||
|
||||
name = args->argv[0];
|
||||
if (*name == '\0') {
|
||||
cmdq_error(cmdq, "invalid hook name");
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
if (args->argc < 2)
|
||||
cmd = NULL;
|
||||
else
|
||||
cmd = args->argv[1];
|
||||
|
||||
if (args_has(args, 'u')) {
|
||||
if (cmd != NULL) {
|
||||
cmdq_error(cmdq, "command passed to unset hook: %s",
|
||||
name);
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
if ((hook = hooks_find(hooks, name)) != NULL)
|
||||
hooks_remove(hooks, hook);
|
||||
return (CMD_RETURN_NORMAL);
|
||||
}
|
||||
|
||||
if (cmd == NULL) {
|
||||
cmdq_error(cmdq, "no command to set hook: %s", name);
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
if (cmd_string_parse(cmd, &cmdlist, NULL, 0, &cause) != 0) {
|
||||
if (cause != NULL) {
|
||||
cmdq_error(cmdq, "%s", cause);
|
||||
free(cause);
|
||||
}
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
hooks_add(hooks, name, cmdlist);
|
||||
cmd_list_free(cmdlist);
|
||||
|
||||
return (CMD_RETURN_NORMAL);
|
||||
}
|
@ -52,12 +52,8 @@ cmd_switch_client_exec(struct cmd *self, struct cmd_q *cmdq)
|
||||
if ((c = cmd_find_client(cmdq, args_get(args, 'c'), 0)) == NULL)
|
||||
return (CMD_RETURN_ERROR);
|
||||
|
||||
if (args_has(args, 'r')) {
|
||||
if (c->flags & CLIENT_READONLY)
|
||||
c->flags &= ~CLIENT_READONLY;
|
||||
else
|
||||
c->flags |= CLIENT_READONLY;
|
||||
}
|
||||
if (args_has(args, 'r'))
|
||||
c->flags ^= CLIENT_READONLY;
|
||||
|
||||
tablename = args_get(args, 'T');
|
||||
if (tablename != NULL) {
|
||||
|
4
cmd.c
4
cmd.c
@ -95,10 +95,12 @@ extern const struct cmd_entry cmd_send_prefix_entry;
|
||||
extern const struct cmd_entry cmd_server_info_entry;
|
||||
extern const struct cmd_entry cmd_set_buffer_entry;
|
||||
extern const struct cmd_entry cmd_set_environment_entry;
|
||||
extern const struct cmd_entry cmd_set_hook_entry;
|
||||
extern const struct cmd_entry cmd_set_option_entry;
|
||||
extern const struct cmd_entry cmd_set_window_option_entry;
|
||||
extern const struct cmd_entry cmd_show_buffer_entry;
|
||||
extern const struct cmd_entry cmd_show_environment_entry;
|
||||
extern const struct cmd_entry cmd_show_hooks_entry;
|
||||
extern const struct cmd_entry cmd_show_messages_entry;
|
||||
extern const struct cmd_entry cmd_show_options_entry;
|
||||
extern const struct cmd_entry cmd_show_window_options_entry;
|
||||
@ -182,10 +184,12 @@ const struct cmd_entry *cmd_table[] = {
|
||||
&cmd_server_info_entry,
|
||||
&cmd_set_buffer_entry,
|
||||
&cmd_set_environment_entry,
|
||||
&cmd_set_hook_entry,
|
||||
&cmd_set_option_entry,
|
||||
&cmd_set_window_option_entry,
|
||||
&cmd_show_buffer_entry,
|
||||
&cmd_show_environment_entry,
|
||||
&cmd_show_hooks_entry,
|
||||
&cmd_show_messages_entry,
|
||||
&cmd_show_options_entry,
|
||||
&cmd_show_window_options_entry,
|
||||
|
139
hooks.c
Normal file
139
hooks.c
Normal file
@ -0,0 +1,139 @@
|
||||
/* $OpenBSD$ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2012 Thomas Adam <thomas@xteddy.org>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
|
||||
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "tmux.h"
|
||||
|
||||
struct hooks {
|
||||
RB_HEAD(hooks_tree, hook) tree;
|
||||
struct hooks *parent;
|
||||
};
|
||||
|
||||
static int hooks_cmp(struct hook *, struct hook *);
|
||||
RB_PROTOTYPE(hooks_tree, hook, entry, hooks_cmp);
|
||||
RB_GENERATE(hooks_tree, hook, entry, hooks_cmp);
|
||||
|
||||
struct hook *hooks_find1(struct hooks *, const char *);
|
||||
|
||||
static int
|
||||
hooks_cmp(struct hook *hook1, struct hook *hook2)
|
||||
{
|
||||
return (strcmp(hook1->name, hook2->name));
|
||||
}
|
||||
|
||||
struct hooks *
|
||||
hooks_create(struct hooks *parent)
|
||||
{
|
||||
struct hooks *hooks;
|
||||
|
||||
hooks = xcalloc(1, sizeof *hooks);
|
||||
RB_INIT(&hooks->tree);
|
||||
hooks->parent = parent;
|
||||
return (hooks);
|
||||
}
|
||||
|
||||
void
|
||||
hooks_free(struct hooks *hooks)
|
||||
{
|
||||
struct hook *hook, *hook1;
|
||||
|
||||
RB_FOREACH_SAFE(hook, hooks_tree, &hooks->tree, hook1)
|
||||
hooks_remove(hooks, hook);
|
||||
free(hooks);
|
||||
}
|
||||
|
||||
struct hook *
|
||||
hooks_first(struct hooks *hooks)
|
||||
{
|
||||
return (RB_MIN(hooks_tree, &hooks->tree));
|
||||
}
|
||||
|
||||
struct hook *
|
||||
hooks_next(struct hook *hook)
|
||||
{
|
||||
return (RB_NEXT(hooks_tree, &hooks->tree, hook));
|
||||
}
|
||||
|
||||
void
|
||||
hooks_add(struct hooks *hooks, const char *name, struct cmd_list *cmdlist)
|
||||
{
|
||||
struct hook *hook;
|
||||
|
||||
if ((hook = hooks_find1(hooks, name)) != NULL)
|
||||
hooks_remove(hooks, hook);
|
||||
|
||||
hook = xcalloc(1, sizeof *hook);
|
||||
hook->name = xstrdup(name);
|
||||
hook->cmdlist = cmdlist;
|
||||
hook->cmdlist->references++;
|
||||
RB_INSERT(hooks_tree, &hooks->tree, hook);
|
||||
}
|
||||
|
||||
void
|
||||
hooks_remove(struct hooks *hooks, struct hook *hook)
|
||||
{
|
||||
RB_REMOVE(hooks_tree, &hooks->tree, hook);
|
||||
cmd_list_free(hook->cmdlist);
|
||||
free((char *) hook->name);
|
||||
free(hook);
|
||||
}
|
||||
|
||||
struct hook *
|
||||
hooks_find1(struct hooks *hooks, const char *name)
|
||||
{
|
||||
struct hook hook;
|
||||
|
||||
hook.name = name;
|
||||
return (RB_FIND(hooks_tree, &hooks->tree, &hook));
|
||||
}
|
||||
|
||||
struct hook *
|
||||
hooks_find(struct hooks *hooks, const char *name)
|
||||
{
|
||||
struct hook hook0, *hook;
|
||||
|
||||
hook0.name = name;
|
||||
hook = RB_FIND(hooks_tree, &hooks->tree, &hook0);
|
||||
while (hook == NULL) {
|
||||
hooks = hooks->parent;
|
||||
if (hooks == NULL)
|
||||
break;
|
||||
hook = RB_FIND(hooks_tree, &hooks->tree, &hook0);
|
||||
}
|
||||
return (hook);
|
||||
}
|
||||
|
||||
void
|
||||
hooks_run(struct hooks *hooks, const char *name, struct client *c)
|
||||
{
|
||||
struct hook *hook;
|
||||
struct cmd_q *cmdq;
|
||||
|
||||
hook = hooks_find(hooks, name);
|
||||
if (hook == NULL)
|
||||
return;
|
||||
log_debug("running hook %s", name);
|
||||
|
||||
cmdq = cmdq_new(c);
|
||||
cmdq_run(cmdq, hook->cmdlist, NULL);
|
||||
cmdq_free(cmdq);
|
||||
}
|
@ -254,6 +254,19 @@ server_client_free(__unused int fd, __unused short events, void *arg)
|
||||
free(c);
|
||||
}
|
||||
|
||||
/* Detach a client. */
|
||||
void
|
||||
server_client_detach(struct client *c, enum msgtype msgtype)
|
||||
{
|
||||
struct session *s = c->session;
|
||||
|
||||
if (s == NULL)
|
||||
return;
|
||||
|
||||
hooks_run(c->session->hooks, "client-detached", c);
|
||||
proc_send_s(c->peer, msgtype, s->name);
|
||||
}
|
||||
|
||||
/* Check for mouse keys. */
|
||||
key_code
|
||||
server_client_check_mouse(struct client *c)
|
||||
@ -1003,6 +1016,8 @@ server_client_dispatch(struct imsg *imsg, void *arg)
|
||||
recalculate_sizes();
|
||||
server_redraw_client(c);
|
||||
}
|
||||
if (c->session != NULL)
|
||||
hooks_run(c->session->hooks, "client-resized", c);
|
||||
break;
|
||||
case MSG_EXITING:
|
||||
if (datalen != 0)
|
||||
|
@ -122,7 +122,9 @@ session_create(const char *name, int argc, char **argv, const char *path,
|
||||
s->environ = environ_create();
|
||||
if (env != NULL)
|
||||
environ_copy(env, s->environ);
|
||||
|
||||
s->options = options_create(global_s_options);
|
||||
s->hooks = hooks_create(global_hooks);
|
||||
|
||||
s->tio = NULL;
|
||||
if (tio != NULL) {
|
||||
@ -188,7 +190,9 @@ session_free(__unused int fd, __unused short events, void *arg)
|
||||
|
||||
if (s->references == 0) {
|
||||
environ_free(s->environ);
|
||||
|
||||
options_free(s->options);
|
||||
hooks_free(s->hooks);
|
||||
|
||||
free(s->name);
|
||||
free(s);
|
||||
|
46
tmux.1
46
tmux.1
@ -3197,6 +3197,52 @@ is used.
|
||||
.Fl v
|
||||
shows only the option value, not the name.
|
||||
.El
|
||||
.Sh HOOKS
|
||||
.Nm
|
||||
allows commands to run on various triggers, called
|
||||
.Em hooks .
|
||||
Each hook has a
|
||||
.Em name .
|
||||
The following hooks are available:
|
||||
.Bl -tag -width "XXXXXXXXXXXXXXXX"
|
||||
.It client-attached
|
||||
Run when a client is attached.
|
||||
.It client-detached
|
||||
Run when a client is detached
|
||||
.It client-resized
|
||||
Run when a client is resized.
|
||||
.El
|
||||
.Pp
|
||||
Hooks are managed with these commands:
|
||||
.Bl -tag -width Ds
|
||||
.It Xo Ic set-hook
|
||||
.Op Fl g
|
||||
.Op Fl t Ar target-session
|
||||
.Ar hook-name
|
||||
.Ar command
|
||||
.Xc
|
||||
Sets hook
|
||||
.Ar hook-name
|
||||
to
|
||||
.Ar command .
|
||||
If
|
||||
.Fl g
|
||||
is given,
|
||||
.Em hook-name
|
||||
is added to the global list of hooks, otherwise it is added to the session
|
||||
hooks (for
|
||||
.Ar target-session
|
||||
with
|
||||
.Fl t ) .
|
||||
Like options, session hooks inherit from the global ones.
|
||||
.It Xo Ic show-hooks
|
||||
.Op Fl g
|
||||
.Op Fl t Ar target-session
|
||||
.Xc
|
||||
Shows the global list of hooks with
|
||||
.Fl g ,
|
||||
otherwise the session hooks.
|
||||
.Ed
|
||||
.Sh MOUSE SUPPORT
|
||||
If the
|
||||
.Ic mouse
|
||||
|
3
tmux.c
3
tmux.c
@ -40,6 +40,7 @@ struct options *global_options; /* server options */
|
||||
struct options *global_s_options; /* session options */
|
||||
struct options *global_w_options; /* window options */
|
||||
struct environ *global_environ;
|
||||
struct hooks *global_hooks;
|
||||
|
||||
struct timeval start_time;
|
||||
const char *socket_path;
|
||||
@ -284,6 +285,8 @@ main(int argc, char **argv)
|
||||
flags |= CLIENT_UTF8;
|
||||
}
|
||||
|
||||
global_hooks = hooks_create(NULL);
|
||||
|
||||
global_environ = environ_create();
|
||||
for (var = environ; *var != NULL; var++)
|
||||
environ_put(global_environ, *var);
|
||||
|
31
tmux.h
31
tmux.h
@ -693,6 +693,14 @@ struct grid {
|
||||
struct grid_line *linedata;
|
||||
};
|
||||
|
||||
/* Hook data structures. */
|
||||
struct hook {
|
||||
const char *name;
|
||||
struct cmd_q *cmdq;
|
||||
struct cmd_list *cmdlist;
|
||||
RB_ENTRY(hook) entry;
|
||||
};
|
||||
|
||||
/* Option data structures. */
|
||||
struct options_entry {
|
||||
char *name;
|
||||
@ -1013,6 +1021,7 @@ struct session {
|
||||
struct winlink_stack lastw;
|
||||
struct winlinks windows;
|
||||
|
||||
struct hooks *hooks;
|
||||
struct options *options;
|
||||
|
||||
#define SESSION_UNATTACHED 0x1 /* not attached to any clients */
|
||||
@ -1429,10 +1438,11 @@ struct options_table_entry {
|
||||
#define CMD_BUFFER_USAGE "[-b buffer-name]"
|
||||
|
||||
/* tmux.c */
|
||||
extern struct options *global_options;
|
||||
extern struct options *global_s_options;
|
||||
extern struct options *global_w_options;
|
||||
extern struct environ *global_environ;
|
||||
extern struct hooks *global_hooks;
|
||||
extern struct options *global_options;
|
||||
extern struct options *global_s_options;
|
||||
extern struct options *global_w_options;
|
||||
extern struct environ *global_environ;
|
||||
extern struct timeval start_time;
|
||||
extern const char *socket_path;
|
||||
const char *getshell(void);
|
||||
@ -1497,6 +1507,18 @@ void format_defaults_pane(struct format_tree *,
|
||||
void format_defaults_paste_buffer(struct format_tree *,
|
||||
struct paste_buffer *);
|
||||
|
||||
/* hooks.c */
|
||||
struct hook;
|
||||
struct hooks *hooks_create(struct hooks *);
|
||||
void hooks_free(struct hooks *);
|
||||
struct hook *hooks_first(struct hooks *);
|
||||
struct hook *hooks_next(struct hook *);
|
||||
void hooks_add(struct hooks *, const char *, struct cmd_list *);
|
||||
void hooks_copy(struct hooks *, struct hooks *);
|
||||
void hooks_remove(struct hooks *, struct hook *);
|
||||
struct hook *hooks_find(struct hooks *, const char *);
|
||||
void hooks_run(struct hooks *, const char *, struct client *);
|
||||
|
||||
/* mode-key.c */
|
||||
extern const struct mode_key_table mode_key_tables[];
|
||||
extern struct mode_key_tree mode_key_tree_vi_edit;
|
||||
@ -1784,6 +1806,7 @@ void server_client_create(int);
|
||||
int server_client_open(struct client *, char **);
|
||||
void server_client_unref(struct client *);
|
||||
void server_client_lost(struct client *);
|
||||
void server_client_detach(struct client *, enum msgtype);
|
||||
void server_client_loop(void);
|
||||
void server_client_push_stdout(struct client *);
|
||||
void server_client_push_stderr(struct client *);
|
||||
|
Loading…
Reference in New Issue
Block a user