Add "machine" criterion to match WM_CLIENT_MACHINE
Closes #3981 Add "%machine" title_format placeholder Add "machine" to the IPC and layout saving/restoring
This commit is contained in:
parent
921226783f
commit
32c10a19f2
@ -17,6 +17,8 @@ strongly encouraged to upgrade.
|
|||||||
• i3bar: use first bar config by default
|
• i3bar: use first bar config by default
|
||||||
• i3-dump-log -f now uses UNIX sockets instead of pthreads. The UNIX socket approach
|
• i3-dump-log -f now uses UNIX sockets instead of pthreads. The UNIX socket approach
|
||||||
should be more reliable and also more portable.
|
should be more reliable and also more portable.
|
||||||
|
• Allow for_window to match against WM_CLIENT_MACHINE
|
||||||
|
• Add %machine placeholder (WM_CLIENT_MACHINE) to title_format
|
||||||
|
|
||||||
┌────────────────────────────┐
|
┌────────────────────────────┐
|
||||||
│ Bugfixes │
|
│ Bugfixes │
|
||||||
|
3
docs/ipc
3
docs/ipc
@ -362,7 +362,8 @@ window (integer)::
|
|||||||
X11-related tools display (usually in hex).
|
X11-related tools display (usually in hex).
|
||||||
window_properties (map)::
|
window_properties (map)::
|
||||||
This optional field contains all available X11 window properties from the
|
This optional field contains all available X11 window properties from the
|
||||||
following list: *title*, *instance*, *class*, *window_role* and *transient_for*.
|
following list: *title*, *instance*, *class*, *window_role*, *machine*
|
||||||
|
and *transient_for*.
|
||||||
window_type (string)::
|
window_type (string)::
|
||||||
The window type (_NET_WM_WINDOW_TYPE). Possible values are undefined, normal,
|
The window type (_NET_WM_WINDOW_TYPE). Possible values are undefined, normal,
|
||||||
dialog, utility, toolbar, splash, menu, dropdown_menu, popup_menu, tooltip and
|
dialog, utility, toolbar, splash, menu, dropdown_menu, popup_menu, tooltip and
|
||||||
|
@ -185,9 +185,9 @@ Therefore, if you just start Emacs via dmenu, it will not get swallowed by that
|
|||||||
container. Only if you start Emacs with the proper instance name (+emacs24
|
container. Only if you start Emacs with the proper instance name (+emacs24
|
||||||
--name notmuch+), it will get swallowed.
|
--name notmuch+), it will get swallowed.
|
||||||
|
|
||||||
You can match on "class", "instance", "window_role" and "title". All values are
|
You can match on "class", "instance", "window_role", "title" and "machine". All
|
||||||
case-sensitive regular expressions (PCRE). Use +xprop(1)+ and click into a
|
values are case-sensitive regular expressions (PCRE). Use +xprop(1)+ and click
|
||||||
window to see its properties:
|
into a window to see its properties:
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
$ xprop
|
$ xprop
|
||||||
|
@ -1896,6 +1896,10 @@ window_type::
|
|||||||
Compare the window type (_NET_WM_WINDOW_TYPE). Possible values are
|
Compare the window type (_NET_WM_WINDOW_TYPE). Possible values are
|
||||||
+normal+, +dialog+, +utility+, +toolbar+, +splash+, +menu+, +dropdown_menu+,
|
+normal+, +dialog+, +utility+, +toolbar+, +splash+, +menu+, +dropdown_menu+,
|
||||||
+popup_menu+, +tooltip+ and +notification+.
|
+popup_menu+, +tooltip+ and +notification+.
|
||||||
|
machine::
|
||||||
|
Compares the name of the machine the client window is running on
|
||||||
|
(WM_CLIENT_MACHINE). Usually, it is equal to the hostname of the local
|
||||||
|
machine, but it may differ if remote X11 apps are used.
|
||||||
id::
|
id::
|
||||||
Compares the X11 window ID, which you can get via +xwininfo+ for example.
|
Compares the X11 window ID, which you can get via +xwininfo+ for example.
|
||||||
title::
|
title::
|
||||||
@ -1933,9 +1937,9 @@ tiling_from::
|
|||||||
tiling are matched. With "user", only windows that the user made tiling
|
tiling are matched. With "user", only windows that the user made tiling
|
||||||
are matched.
|
are matched.
|
||||||
|
|
||||||
The criteria +class+, +instance+, +role+, +title+, +workspace+ and +mark+ are
|
The criteria +class+, +instance+, +role+, +title+, +workspace+, +machine+ and
|
||||||
actually regular expressions (PCRE). See +pcresyntax(3)+ or +perldoc perlre+ for
|
+mark+ are actually regular expressions (PCRE). See +pcresyntax(3)+ or +perldoc
|
||||||
information on how to use them.
|
perlre+ for information on how to use them.
|
||||||
|
|
||||||
[[exec]]
|
[[exec]]
|
||||||
=== Executing applications (exec)
|
=== Executing applications (exec)
|
||||||
@ -2584,6 +2588,9 @@ and the following placeholders which will be replaced:
|
|||||||
+%instance+::
|
+%instance+::
|
||||||
The X11 window instance (first part of WM_CLASS). This corresponds to the
|
The X11 window instance (first part of WM_CLASS). This corresponds to the
|
||||||
+instance+ criterion, see <<command_criteria>>.
|
+instance+ criterion, see <<command_criteria>>.
|
||||||
|
+%machine+::
|
||||||
|
The X11 name of the machine (WM_CLIENT_MACHINE). This corresponds to the
|
||||||
|
+machine+ criterion, see <<command_criteria>>.
|
||||||
|
|
||||||
Using the <<for_window>> directive, you can set the title format for any window
|
Using the <<for_window>> directive, you can set the title format for any window
|
||||||
based on <<command_criteria>>.
|
based on <<command_criteria>>.
|
||||||
|
@ -414,6 +414,9 @@ struct Window {
|
|||||||
* for_window. */
|
* for_window. */
|
||||||
char *role;
|
char *role;
|
||||||
|
|
||||||
|
/** WM_CLIENT_MACHINE of the window */
|
||||||
|
char *machine;
|
||||||
|
|
||||||
/** Flag to force re-rendering the decoration upon changes */
|
/** Flag to force re-rendering the decoration upon changes */
|
||||||
bool name_x_changed;
|
bool name_x_changed;
|
||||||
|
|
||||||
@ -500,6 +503,7 @@ struct Match {
|
|||||||
struct regex *mark;
|
struct regex *mark;
|
||||||
struct regex *window_role;
|
struct regex *window_role;
|
||||||
struct regex *workspace;
|
struct regex *workspace;
|
||||||
|
struct regex *machine;
|
||||||
xcb_atom_t window_type;
|
xcb_atom_t window_type;
|
||||||
enum {
|
enum {
|
||||||
U_DONTCHECK = -1,
|
U_DONTCHECK = -1,
|
||||||
|
@ -95,3 +95,9 @@ void window_update_hints(i3Window *win, xcb_get_property_reply_t *prop, bool *ur
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
void window_update_motif_hints(i3Window *win, xcb_get_property_reply_t *prop, border_style_t *motif_border_style);
|
void window_update_motif_hints(i3Window *win, xcb_get_property_reply_t *prop, border_style_t *motif_border_style);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the WM_CLIENT_MACHINE
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void window_update_machine(i3Window *win, xcb_get_property_reply_t *prop);
|
||||||
|
@ -54,6 +54,7 @@ state CRITERIA:
|
|||||||
ctype = 'title' -> CRITERION
|
ctype = 'title' -> CRITERION
|
||||||
ctype = 'urgent' -> CRITERION
|
ctype = 'urgent' -> CRITERION
|
||||||
ctype = 'workspace' -> CRITERION
|
ctype = 'workspace' -> CRITERION
|
||||||
|
ctype = 'machine' -> CRITERION
|
||||||
ctype = 'tiling', 'floating'
|
ctype = 'tiling', 'floating'
|
||||||
-> call cmd_criteria_add($ctype, NULL); CRITERIA
|
-> call cmd_criteria_add($ctype, NULL); CRITERIA
|
||||||
']' -> call cmd_criteria_match_windows(); INITIAL
|
']' -> call cmd_criteria_match_windows(); INITIAL
|
||||||
|
@ -191,6 +191,7 @@ state CRITERIA:
|
|||||||
ctype = 'title' -> CRITERION
|
ctype = 'title' -> CRITERION
|
||||||
ctype = 'urgent' -> CRITERION
|
ctype = 'urgent' -> CRITERION
|
||||||
ctype = 'workspace' -> CRITERION
|
ctype = 'workspace' -> CRITERION
|
||||||
|
ctype = 'machine' -> CRITERION
|
||||||
ctype = 'floating_from' -> CRITERION_FROM
|
ctype = 'floating_from' -> CRITERION_FROM
|
||||||
ctype = 'tiling_from' -> CRITERION_FROM
|
ctype = 'tiling_from' -> CRITERION_FROM
|
||||||
ctype = 'tiling', 'floating'
|
ctype = 'tiling', 'floating'
|
||||||
|
@ -2304,20 +2304,25 @@ i3String *con_parse_title_format(Con *con) {
|
|||||||
char *title;
|
char *title;
|
||||||
char *class;
|
char *class;
|
||||||
char *instance;
|
char *instance;
|
||||||
|
char *machine;
|
||||||
if (win == NULL) {
|
if (win == NULL) {
|
||||||
title = pango_escape_markup(con_get_tree_representation(con));
|
title = pango_escape_markup(con_get_tree_representation(con));
|
||||||
class = sstrdup("i3-frame");
|
class = sstrdup("i3-frame");
|
||||||
instance = sstrdup("i3-frame");
|
instance = sstrdup("i3-frame");
|
||||||
|
machine = sstrdup("");
|
||||||
} else {
|
} else {
|
||||||
title = pango_escape_markup(sstrdup((win->name == NULL) ? "" : i3string_as_utf8(win->name)));
|
title = pango_escape_markup(sstrdup((win->name == NULL) ? "" : i3string_as_utf8(win->name)));
|
||||||
class = pango_escape_markup(sstrdup((win->class_class == NULL) ? "" : win->class_class));
|
class = pango_escape_markup(sstrdup((win->class_class == NULL) ? "" : win->class_class));
|
||||||
instance = pango_escape_markup(sstrdup((win->class_instance == NULL) ? "" : win->class_instance));
|
instance = pango_escape_markup(sstrdup((win->class_instance == NULL) ? "" : win->class_instance));
|
||||||
|
machine = pango_escape_markup(sstrdup((win->machine == NULL) ? "" : win->machine));
|
||||||
}
|
}
|
||||||
|
|
||||||
placeholder_t placeholders[] = {
|
placeholder_t placeholders[] = {
|
||||||
{.name = "%title", .value = title},
|
{.name = "%title", .value = title},
|
||||||
{.name = "%class", .value = class},
|
{.name = "%class", .value = class},
|
||||||
{.name = "%instance", .value = instance}};
|
{.name = "%instance", .value = instance},
|
||||||
|
{.name = "%machine", .value = machine},
|
||||||
|
};
|
||||||
const size_t num = sizeof(placeholders) / sizeof(placeholder_t);
|
const size_t num = sizeof(placeholders) / sizeof(placeholder_t);
|
||||||
|
|
||||||
char *formatted_str = format_placeholders(con->title_format, &placeholders[0], num);
|
char *formatted_str = format_placeholders(con->title_format, &placeholders[0], num);
|
||||||
|
@ -1086,6 +1086,16 @@ static bool handle_class_change(Con *con, xcb_get_property_reply_t *prop) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Handles the WM_CLIENT_MACHINE property for assignments and criteria selection.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
static bool handle_machine_change(Con *con, xcb_get_property_reply_t *prop) {
|
||||||
|
window_update_machine(con->window, prop);
|
||||||
|
con = remanage_window(con);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Handles the _MOTIF_WM_HINTS property of specifing window deocration settings.
|
* Handles the _MOTIF_WM_HINTS property of specifing window deocration settings.
|
||||||
*
|
*
|
||||||
@ -1197,6 +1207,7 @@ static struct property_handler_t property_handlers[] = {
|
|||||||
{0, UINT_MAX, handle_strut_partial_change},
|
{0, UINT_MAX, handle_strut_partial_change},
|
||||||
{0, UINT_MAX, handle_window_type},
|
{0, UINT_MAX, handle_window_type},
|
||||||
{0, UINT_MAX, handle_i3_floating},
|
{0, UINT_MAX, handle_i3_floating},
|
||||||
|
{0, 128, handle_machine_change},
|
||||||
{0, 5 * sizeof(uint64_t), handle_motif_hints_change}};
|
{0, 5 * sizeof(uint64_t), handle_motif_hints_change}};
|
||||||
#define NUM_HANDLERS (sizeof(property_handlers) / sizeof(struct property_handler_t))
|
#define NUM_HANDLERS (sizeof(property_handlers) / sizeof(struct property_handler_t))
|
||||||
|
|
||||||
@ -1219,7 +1230,8 @@ void property_handlers_init(void) {
|
|||||||
property_handlers[8].atom = A__NET_WM_STRUT_PARTIAL;
|
property_handlers[8].atom = A__NET_WM_STRUT_PARTIAL;
|
||||||
property_handlers[9].atom = A__NET_WM_WINDOW_TYPE;
|
property_handlers[9].atom = A__NET_WM_WINDOW_TYPE;
|
||||||
property_handlers[10].atom = A_I3_FLOATING_WINDOW;
|
property_handlers[10].atom = A_I3_FLOATING_WINDOW;
|
||||||
property_handlers[11].atom = A__MOTIF_WM_HINTS;
|
property_handlers[11].atom = XCB_ATOM_WM_CLIENT_MACHINE;
|
||||||
|
property_handlers[12].atom = A__MOTIF_WM_HINTS;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void property_notify(uint8_t state, xcb_window_t window, xcb_atom_t atom) {
|
static void property_notify(uint8_t state, xcb_window_t window, xcb_atom_t atom) {
|
||||||
|
@ -557,6 +557,7 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
|
|||||||
DUMP_PROPERTY("class", class_class);
|
DUMP_PROPERTY("class", class_class);
|
||||||
DUMP_PROPERTY("instance", class_instance);
|
DUMP_PROPERTY("instance", class_instance);
|
||||||
DUMP_PROPERTY("window_role", role);
|
DUMP_PROPERTY("window_role", role);
|
||||||
|
DUMP_PROPERTY("machine", machine);
|
||||||
|
|
||||||
if (con->window->name != NULL) {
|
if (con->window->name != NULL) {
|
||||||
ystr("title");
|
ystr("title");
|
||||||
@ -646,6 +647,7 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
|
|||||||
DUMP_REGEX(instance);
|
DUMP_REGEX(instance);
|
||||||
DUMP_REGEX(window_role);
|
DUMP_REGEX(window_role);
|
||||||
DUMP_REGEX(title);
|
DUMP_REGEX(title);
|
||||||
|
DUMP_REGEX(machine);
|
||||||
|
|
||||||
#undef DUMP_REGEX
|
#undef DUMP_REGEX
|
||||||
y(map_close);
|
y(map_close);
|
||||||
|
@ -285,6 +285,9 @@ static int json_string(void *ctx, const unsigned char *val, size_t len) {
|
|||||||
} else if (strcasecmp(last_key, "title") == 0) {
|
} else if (strcasecmp(last_key, "title") == 0) {
|
||||||
current_swallow->title = regex_new(sval);
|
current_swallow->title = regex_new(sval);
|
||||||
swallow_is_empty = false;
|
swallow_is_empty = false;
|
||||||
|
} else if (strcasecmp(last_key, "machine") == 0) {
|
||||||
|
current_swallow->machine = regex_new(sval);
|
||||||
|
swallow_is_empty = false;
|
||||||
} else {
|
} else {
|
||||||
ELOG("swallow key %s unknown\n", last_key);
|
ELOG("swallow key %s unknown\n", last_key);
|
||||||
}
|
}
|
||||||
|
@ -116,7 +116,8 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
|
|||||||
utf8_title_cookie, title_cookie,
|
utf8_title_cookie, title_cookie,
|
||||||
class_cookie, leader_cookie, transient_cookie,
|
class_cookie, leader_cookie, transient_cookie,
|
||||||
role_cookie, startup_id_cookie, wm_hints_cookie,
|
role_cookie, startup_id_cookie, wm_hints_cookie,
|
||||||
wm_normal_hints_cookie, motif_wm_hints_cookie, wm_user_time_cookie, wm_desktop_cookie;
|
wm_normal_hints_cookie, motif_wm_hints_cookie, wm_user_time_cookie, wm_desktop_cookie,
|
||||||
|
wm_machine_cookie;
|
||||||
|
|
||||||
geomc = xcb_get_geometry(conn, d);
|
geomc = xcb_get_geometry(conn, d);
|
||||||
|
|
||||||
@ -189,6 +190,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
|
|||||||
motif_wm_hints_cookie = GET_PROPERTY(A__MOTIF_WM_HINTS, 5 * sizeof(uint64_t));
|
motif_wm_hints_cookie = GET_PROPERTY(A__MOTIF_WM_HINTS, 5 * sizeof(uint64_t));
|
||||||
wm_user_time_cookie = GET_PROPERTY(A__NET_WM_USER_TIME, UINT32_MAX);
|
wm_user_time_cookie = GET_PROPERTY(A__NET_WM_USER_TIME, UINT32_MAX);
|
||||||
wm_desktop_cookie = GET_PROPERTY(A__NET_WM_DESKTOP, UINT32_MAX);
|
wm_desktop_cookie = GET_PROPERTY(A__NET_WM_DESKTOP, UINT32_MAX);
|
||||||
|
wm_machine_cookie = GET_PROPERTY(XCB_ATOM_WM_CLIENT_MACHINE, UINT32_MAX);
|
||||||
|
|
||||||
i3Window *cwindow = scalloc(1, sizeof(i3Window));
|
i3Window *cwindow = scalloc(1, sizeof(i3Window));
|
||||||
cwindow->id = window;
|
cwindow->id = window;
|
||||||
@ -211,6 +213,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
|
|||||||
border_style_t motif_border_style = BS_NORMAL;
|
border_style_t motif_border_style = BS_NORMAL;
|
||||||
window_update_motif_hints(cwindow, xcb_get_property_reply(conn, motif_wm_hints_cookie, NULL), &motif_border_style);
|
window_update_motif_hints(cwindow, xcb_get_property_reply(conn, motif_wm_hints_cookie, NULL), &motif_border_style);
|
||||||
window_update_normal_hints(cwindow, xcb_get_property_reply(conn, wm_normal_hints_cookie, NULL), geom);
|
window_update_normal_hints(cwindow, xcb_get_property_reply(conn, wm_normal_hints_cookie, NULL), geom);
|
||||||
|
window_update_machine(cwindow, xcb_get_property_reply(conn, wm_machine_cookie, NULL));
|
||||||
xcb_get_property_reply_t *type_reply = xcb_get_property_reply(conn, wm_type_cookie, NULL);
|
xcb_get_property_reply_t *type_reply = xcb_get_property_reply(conn, wm_type_cookie, NULL);
|
||||||
xcb_get_property_reply_t *state_reply = xcb_get_property_reply(conn, state_cookie, NULL);
|
xcb_get_property_reply_t *state_reply = xcb_get_property_reply(conn, state_cookie, NULL);
|
||||||
|
|
||||||
|
10
src/match.c
10
src/match.c
@ -47,6 +47,7 @@ bool match_is_empty(Match *match) {
|
|||||||
match->instance == NULL &&
|
match->instance == NULL &&
|
||||||
match->window_role == NULL &&
|
match->window_role == NULL &&
|
||||||
match->workspace == NULL &&
|
match->workspace == NULL &&
|
||||||
|
match->machine == NULL &&
|
||||||
match->urgent == U_DONTCHECK &&
|
match->urgent == U_DONTCHECK &&
|
||||||
match->id == XCB_NONE &&
|
match->id == XCB_NONE &&
|
||||||
match->window_type == UINT32_MAX &&
|
match->window_type == UINT32_MAX &&
|
||||||
@ -130,6 +131,8 @@ bool match_matches_window(Match *match, i3Window *window) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CHECK_WINDOW_FIELD(machine, machine, str);
|
||||||
|
|
||||||
Con *con = NULL;
|
Con *con = NULL;
|
||||||
if (match->urgent == U_LATEST) {
|
if (match->urgent == U_LATEST) {
|
||||||
/* if the window isn't urgent, no sense in searching */
|
/* if the window isn't urgent, no sense in searching */
|
||||||
@ -273,6 +276,7 @@ void match_free(Match *match) {
|
|||||||
regex_free(match->mark);
|
regex_free(match->mark);
|
||||||
regex_free(match->window_role);
|
regex_free(match->window_role);
|
||||||
regex_free(match->workspace);
|
regex_free(match->workspace);
|
||||||
|
regex_free(match->machine);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -390,6 +394,12 @@ void match_parse_property(Match *match, const char *ctype, const char *cvalue) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (strcmp(ctype, "machine") == 0) {
|
||||||
|
regex_free(match->machine);
|
||||||
|
match->machine = regex_new(cvalue);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (strcmp(ctype, "tiling") == 0) {
|
if (strcmp(ctype, "tiling") == 0) {
|
||||||
match->window_mode = WM_TILING;
|
match->window_mode = WM_TILING;
|
||||||
return;
|
return;
|
||||||
|
@ -153,6 +153,7 @@ static void update_placeholder_contents(placeholder_state *state) {
|
|||||||
APPEND_REGEX(instance);
|
APPEND_REGEX(instance);
|
||||||
APPEND_REGEX(window_role);
|
APPEND_REGEX(window_role);
|
||||||
APPEND_REGEX(title);
|
APPEND_REGEX(title);
|
||||||
|
APPEND_REGEX(machine);
|
||||||
|
|
||||||
if (serialized == NULL) {
|
if (serialized == NULL) {
|
||||||
DLOG("This swallows specification is not serializable?!\n");
|
DLOG("This swallows specification is not serializable?!\n");
|
||||||
|
18
src/window.c
18
src/window.c
@ -466,3 +466,21 @@ void window_update_motif_hints(i3Window *win, xcb_get_property_reply_t *prop, bo
|
|||||||
#undef MWM_DECOR_BORDER
|
#undef MWM_DECOR_BORDER
|
||||||
#undef MWM_DECOR_TITLE
|
#undef MWM_DECOR_TITLE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Updates the WM_CLIENT_MACHINE
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void window_update_machine(i3Window *win, xcb_get_property_reply_t *prop) {
|
||||||
|
if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
|
||||||
|
DLOG("WM_CLIENT_MACHINE not set.\n");
|
||||||
|
FREE(prop);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
FREE(win->machine);
|
||||||
|
win->machine = sstrndup((char *)xcb_get_property_value(prop), xcb_get_property_value_length(prop));
|
||||||
|
LOG("WM_CLIENT_MACHINE changed to \"%s\"\n", win->machine);
|
||||||
|
|
||||||
|
free(prop);
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user