docs: Wayland
One popup per session, so the frontend is picked at startup: a compositor with zwp_input_method_v2 gets that frontend and neither XIM nor the X11 popup is started. The answer for a user is one sentence -- on Wayland set none of the module variables -- and the two exceptions the plan asked to check turned out this way: GTK 4 binds text-input-v3 by itself, with GTK_IM_MODULE unset (4.22 under sway 1.12, typed and composed), and Chromium is beyond help either way, since it asks for text-input-v1 and wlroots implements only v3. The plan is deleted, as it said to be once the README described what landed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
47
README.md
47
README.md
@@ -1,8 +1,10 @@
|
||||
# strans
|
||||
|
||||
strans is a small, single-user input method for Korean, Japanese, English,
|
||||
emoji, and symbols. It provides one engine for IBus (which GTK 4 and
|
||||
Qt use), XIM, and GTK 3. Vietnamese Telex is also available as a
|
||||
emoji, and symbols. It provides one engine for Wayland compositors that
|
||||
speak `zwp_input_method_v2` — the wlroots and smithay families, so sway,
|
||||
river, labwc, Wayfire, dwl, niri and COSMIC — and for IBus (which GTK 4
|
||||
and Qt use), XIM, and GTK 3. Vietnamese Telex is also available as a
|
||||
compatibility mode.
|
||||
|
||||
## Input modes
|
||||
@@ -72,6 +74,7 @@ clicking elsewhere commits what is pending.
|
||||
|
||||
| Frontend | Preedit | Candidates |
|
||||
| --- | --- | --- |
|
||||
| Wayland input method | inline in the client | strans popup |
|
||||
| GTK 3 and IBus | inline in the client | strans popup |
|
||||
| XIM PreeditCallbacks | inline through XIM callbacks | strans popup |
|
||||
| XIM PreeditPosition | strans popup | strans popup |
|
||||
@@ -90,11 +93,13 @@ style, and above the line when there is no room below; otherwise the popup
|
||||
sits under the focus window, or under the client window when no focus
|
||||
window is set.
|
||||
|
||||
The popup is an X11 window sized by `GDK_SCALE` on HiDPI displays, XIM is
|
||||
X11-only, and GTK popup placement converts the GTK caret to X11 root
|
||||
coordinates. There is no native Wayland popup surface or Wayland caret
|
||||
positioning. GTK and IBus clients can still use inline preedit where their
|
||||
display environment supports it.
|
||||
On a compositor with `zwp_input_method_v2` the popup is a Wayland surface
|
||||
that the compositor itself puts at the text cursor, flipping it above the
|
||||
line when there is no room below; a client that reports no cursor location
|
||||
gets it under its window instead. Everywhere else the popup is an X11 window,
|
||||
placed from the XIM spot or from the GTK caret converted to root
|
||||
coordinates, and XIM is X11-only. Either way `GDK_SCALE` sizes it for
|
||||
HiDPI displays.
|
||||
|
||||
## Build and test
|
||||
|
||||
@@ -109,7 +114,7 @@ make docker-build
|
||||
Build output:
|
||||
|
||||
```text
|
||||
strans daemon with IBus and XIM frontends
|
||||
strans daemon with Wayland, IBus, and XIM frontends
|
||||
gtk/im-strans.so GTK 3 frontend module
|
||||
```
|
||||
|
||||
@@ -133,6 +138,8 @@ development files for:
|
||||
- Plan 9 port (`9c`, `9l`, `libthread`, and `libbio`);
|
||||
- D-Bus development files;
|
||||
- XCB, XCB RandR, XCB XKB, xcb-util, xcb-imdkit, xkbcommon, and xkbcommon-x11;
|
||||
- the Wayland client library and `wayland-scanner`, which writes the
|
||||
input-method and virtual-keyboard bindings from the XML in `proto/`;
|
||||
- Pango, PangoCairo, Cairo, and Fontconfig;
|
||||
- GTK 3, plus libibus and Xlib for the live clients;
|
||||
- Xvfb for the X11 live tests;
|
||||
@@ -158,8 +165,9 @@ build-tree daemon in the background, and returns to the shell. It requires
|
||||
`pkill` from procps. Startup and frontend failures remain visible on the
|
||||
caller's standard error. A service supervisor should instead run
|
||||
`./strans map` directly in the foreground, with the session's
|
||||
`XDG_RUNTIME_DIR` and `DISPLAY`: the GTK module finds the daemon at
|
||||
`$XDG_RUNTIME_DIR/strans.sock` (else `/tmp/strans.UID`), and IBus clients
|
||||
`XDG_RUNTIME_DIR` and its `WAYLAND_DISPLAY` or `DISPLAY`: the GTK module
|
||||
finds the daemon at `$XDG_RUNTIME_DIR/strans.sock` (else
|
||||
`/tmp/strans.UID`), and IBus clients
|
||||
through the address file libibus expects under `~/.config/ibus/bus/`,
|
||||
which strans writes as `ibus-daemon` would, named after `WAYLAND_DISPLAY`
|
||||
when there is one and `DISPLAY` otherwise.
|
||||
@@ -169,6 +177,20 @@ opens `hira.map`, `kata.map`, `telex.map`, `kanji.dict`, `emoji.dict`, and
|
||||
`hanja.dict` from `DIR` at runtime; `run.sh` passes the tracked `map/`
|
||||
directory. Only the GTK subdirectory has install and uninstall targets.
|
||||
|
||||
strans shows one popup per session, so it picks its frontend at startup: a
|
||||
session whose compositor offers `zwp_input_method_v2` gets that frontend,
|
||||
and neither XIM nor the X11 popup is started; every other session gets XIM
|
||||
as before. The IBus endpoint and the GTK 3 module are served in both.
|
||||
|
||||
On Wayland, then, set none of the variables below. GTK, Qt and Firefox
|
||||
speak text-input-v3 themselves and need nothing — GTK 4 binds it without
|
||||
`GTK_IM_MODULE`, checked with 4.22 under sway 1.12 — and `GTK_IM_MODULE`
|
||||
is what pushes them off the path that works. A client that reaches strans
|
||||
over IBus or the GTK 3 module while the Wayland frontend runs still
|
||||
composes inline, but shows no candidate list, because the popup belongs to
|
||||
that frontend. Chromium is helped by neither: it still asks for
|
||||
`text-input-v1`, which wlroots does not implement at all.
|
||||
|
||||
Configure clients as needed:
|
||||
|
||||
```sh
|
||||
@@ -201,8 +223,9 @@ doas make -C gtk uninstall
|
||||
strans provides its own IBus endpoint; `ibus-daemon` and fcitx are not
|
||||
required. IBus clients that process keys synchronously — GTK 4, or GTK 3
|
||||
with `IBUS_ENABLE_SYNC_MODE=1` — get their commits and preedits through
|
||||
`PostProcessKeyEvent`, in order with the key. Without `DISPLAY`, the daemon and IBus frontend still work, but XIM
|
||||
is not started and the popup is disabled.
|
||||
`PostProcessKeyEvent`, in order with the key. With neither
|
||||
`WAYLAND_DISPLAY` nor `DISPLAY`, the daemon, its IBus endpoint and its
|
||||
socket still work, but no frontend draws a popup.
|
||||
|
||||
## Dictionary data
|
||||
|
||||
|
||||
301
WAYLAND.md
301
WAYLAND.md
@@ -1,301 +0,0 @@
|
||||
# Wayland input method: design and plan
|
||||
|
||||
Plan for work not yet written. Delete this file once the work has landed and
|
||||
the README describes what is there.
|
||||
|
||||
## 1. What is wrong today
|
||||
|
||||
The popup is X11 only. `win.c` is the sole consumer of `drawc`, it connects
|
||||
with `xcb_connect`, and when that fails it prints `popup disabled` and
|
||||
returns (`win.c:206`). Under a Wayland session with no X server there are no
|
||||
candidates at all, which makes Kanji, Hanja and Emoji unusable. Preedit still
|
||||
works for clients that reach us over IBus or the GTK module.
|
||||
|
||||
A second, smaller fault: libibus sends `SetCursorLocationRelative` instead of
|
||||
`SetCursorLocation` when the client runs on Wayland (ibus
|
||||
`client/gtk3/ibusimcontext.c:1767`). `ictab` (`ibus.c:904`) does not list it,
|
||||
so libdbus answers `UnknownMethod` and the caret never arrives.
|
||||
|
||||
## 2. What to implement, and what not
|
||||
|
||||
Implement `zwp_input_method_v2` with `zwp_virtual_keyboard_v1`. One protocol
|
||||
covers every compositor we can serve:
|
||||
|
||||
| Compositor | Protocol |
|
||||
| --- | --- |
|
||||
| wlroots: sway, river, labwc, Wayfire, dwl | `zwp_input_method_v2` |
|
||||
| smithay: niri, COSMIC | `zwp_input_method_v2` |
|
||||
| KWin | `zwp_input_method_v1` only |
|
||||
| GNOME | none |
|
||||
|
||||
Not `zwp_input_method_v1`: it buys KWin alone, and KWin starts the input
|
||||
method itself from its own configuration.
|
||||
|
||||
Not GNOME: it has no input-method protocol, and our IBus endpoint writes the
|
||||
address file that gnome-shell's own ibus-daemon owns. strans cannot work
|
||||
there whatever we write.
|
||||
|
||||
`experimental/xx-input-method` in wayland-protocols is where this is heading:
|
||||
a popup positioner and a per-key consume/passthrough filter that matches our
|
||||
engine exactly. No compositor implements it (checked: wlroots, smithay, KWin
|
||||
trees have no trace of it). Revisit once v2 works and we have something to
|
||||
say about it.
|
||||
|
||||
## 3. Design
|
||||
|
||||
### 3.1 One new file
|
||||
|
||||
`wl.c`, about 600 lines, holding the frontend and its popup. Two protocol XML
|
||||
files are vendored under `proto/` and turned into `imv2.[ch]` and `vkv1.[ch]`
|
||||
by wayland-scanner at build time; only the XML is tracked.
|
||||
|
||||
The popup lives in `wl.c` rather than beside `win.c` because
|
||||
`get_input_popup_surface` is a request on the input-method object: the popup
|
||||
surface must come from the same connection. Splitting it would mean exporting
|
||||
the connection, which is a worse trade than breaking the symmetry.
|
||||
|
||||
### 3.2 No drawing process
|
||||
|
||||
`imhandlekey` produces the picture before it answers:
|
||||
|
||||
```c
|
||||
redraw(); /* strans.c:999, channbsend(drawc, &dc) */
|
||||
...
|
||||
chansend(kr->reply, &res); /* strans.c:1006 */
|
||||
```
|
||||
|
||||
So by the time a frontend has its `Keyres`, the picture it caused is already
|
||||
in `drawc`, and `redraw` drains its own stale sends before pushing a new one
|
||||
(`strans.c:257`), so `drawc` holds at most one, and that one is the engine's
|
||||
current state. `wl.c` therefore draws inline:
|
||||
|
||||
```c
|
||||
sendrequest(Keypress, ks, mod, &res);
|
||||
...
|
||||
if(channbrecv(drawc, &dc) > 0)
|
||||
popup(&dc);
|
||||
```
|
||||
|
||||
after `Keypress`, always take and draw; after the `Keycap` owner poll, draw
|
||||
only when `res.eaten` says the engine is still ours, else hide.
|
||||
|
||||
The consequence is the point: **one process touches libwayland**. It reads
|
||||
events, dispatches them, draws, and handles `wl_buffer.release`, all in
|
||||
order. No `wl_display_prepare_read` dance, no mutex, no pipe from a channel
|
||||
into a poll loop. fcitx5 spends `waylandeventreader.cpp`, 144 lines of thread
|
||||
plus mutex plus condition variable, on exactly this problem, because its
|
||||
reader and its dispatcher are different threads. We do not have that problem
|
||||
and must not invent it.
|
||||
|
||||
`main.c` gains one `proccreate` and `fn.h` one line. `drawthread` is not
|
||||
started under Wayland, so `drawc` keeps its single consumer.
|
||||
|
||||
### 3.3 One popup per session
|
||||
|
||||
```c
|
||||
/* main.c */
|
||||
if(getenv("WAYLAND_DISPLAY") != nil)
|
||||
proccreate(wlthread, nil, 32768);
|
||||
else{
|
||||
proccreate(drawthread, nil, 16384);
|
||||
if(getenv("DISPLAY") != nil)
|
||||
proccreate(ximthread, nil, 32768);
|
||||
}
|
||||
```
|
||||
|
||||
`srvthread` and `ibusthread` start as they do now, in both cases.
|
||||
|
||||
What this costs: XIM is not served under Wayland, and clients that reach us
|
||||
over IBus or the GTK module while on Wayland get inline preedit with no
|
||||
candidate list. Both are documented, and the answer for a user is the same
|
||||
one sentence: **on Wayland, set none of the X11 variables.** GTK, Qt and
|
||||
Firefox speak text-input-v3 themselves; `GTK_IM_MODULE` is what pushes them
|
||||
off the path that works. Two exceptions to check while writing commit 5:
|
||||
GTK 4 has been reported in 2026 not to bind `zwp_text_input_manager_v3`
|
||||
unless `GTK_IM_MODULE=wayland` is set, and Chromium still defaults to
|
||||
text-input-v1.
|
||||
|
||||
### 3.4 Keys
|
||||
|
||||
* `activate`, `deactivate` and `content_type` are pending state applied at
|
||||
`done`; count `done` events, that count is the serial `commit` takes.
|
||||
* Grab the keyboard on every activate, releasing the previous grab first. Two
|
||||
activates can arrive without a deactivate between them when focus moves
|
||||
between clients, and the second `grab_keyboard` then fails and leaves a
|
||||
dead object.
|
||||
* `keymap` gives an fd: compile it with `xkb_keymap_new_from_string` and hand
|
||||
the same fd to `zwp_virtual_keyboard_v1.keymap`, so a key we forward means
|
||||
what it meant.
|
||||
* `key` gives an evdev code: `xkb_state_key_get_one_sym(state, code+8)`, then
|
||||
`composekey` (shared with the XIM and IBus frontends, no new table), then
|
||||
`ipckeysym` and the modifier mask to the engine.
|
||||
* A key the engine did not eat goes back through
|
||||
`zwp_virtual_keyboard_v1.key`. Record forwarded codes in a `u32int
|
||||
sent[8]` bitmap: eat the release of a press we ate, and release whatever is
|
||||
still down when the input method deactivates, or the client is left holding
|
||||
a key.
|
||||
* The virtual keyboard must be created on the same `wl_display` as the input
|
||||
method. Nothing in the protocol says so; sway skips the grab for a virtual
|
||||
keyboard whose client owns the grab (`sway/input/keyboard.c:419-431`, with
|
||||
a TODO pointing at wlroots#2322), and on another connection our forwarded
|
||||
keys would come straight back to us. fcitx5 relies on the same contract.
|
||||
* Order within a key: `commit_string`, `set_preedit_string`, `commit(serial)`,
|
||||
then `vk.key`, then one flush. Requests on one connection are processed in
|
||||
order, which is what keeps a commit ahead of the key that caused it.
|
||||
|
||||
We do not commit ordinary keys as text. fcitx5 does by default
|
||||
(`waylandimserverbase.cpp:35-48`, unmodified printable keys become
|
||||
`commit_string`), to dodge exactly the ordering question above. Our `eaten =
|
||||
0` means "this key is not mine", and a key event is the honest answer to
|
||||
that; committing text would lie to terminals and to anything with a
|
||||
keybinding. If ordering does break on some compositor, that workaround is
|
||||
known and can be added then.
|
||||
|
||||
### 3.5 Popup
|
||||
|
||||
* One `wl_surface` and one `zwp_input_popup_surface_v2`, created once. The
|
||||
compositor makes them visible on activate and invisible on deactivate, and
|
||||
places them at the text cursor: `popuparea` and `popupposition` are not
|
||||
used here and stay X11-only.
|
||||
* Two shm buffers, used in turn, each busy from `attach` until
|
||||
`wl_buffer.release`. `win.c`'s single grow-only image would be redrawn
|
||||
while the compositor was still reading it.
|
||||
* `WL_SHM_FORMAT_XRGB8888` is `CAIRO_FORMAT_RGB24` on a little-endian host,
|
||||
so `popupdraw` writes the buffer directly, as it does for X11. `win.c`
|
||||
already checks the same assumption for the X server (`validformat`).
|
||||
* Hide by attaching a nil buffer and committing. If a compositor keeps a
|
||||
stale popup, destroy the surface instead, which is what fcitx5 does
|
||||
(`waylandinputwindow.cpp:158-168`).
|
||||
* `popuplayout` wants an area: take it from the `wl_output` the surface
|
||||
entered, else the first output.
|
||||
* Scale stays `popupscale` from `GDK_SCALE`, as on X11.
|
||||
`wl_surface.preferred_buffer_scale` is the better answer, later.
|
||||
|
||||
### 3.6 What does not change
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| `strans.c`, `Keyreq`, `Keyres`, `Drawcmd`, `ipc.*` | nothing |
|
||||
| `popup_layout.c`, `font.c` | nothing |
|
||||
| `ibus.c`, `srv.c`, `xim.c`, `gtk/` | nothing, beyond the one-line IBus fix |
|
||||
| `dat.h` | `Ibuspurposepassword`, `Ibuspurposepin` move out of `ibus.c` as `Purposepassword`, `Purposepin`; both frontends read the same two numbers |
|
||||
| `main.c` | frontend choice |
|
||||
| `fn.h` | `void wlthread(void*);` |
|
||||
|
||||
The engine needing no change is the argument that this is the right shape.
|
||||
`Keyres.eaten` is already consume-or-pass, `Keyres.commit` is already
|
||||
`commit_string`, `Keyres.preedit` is already `set_preedit_string`, and
|
||||
`clientpre` is simply always true here, which leaves the popup showing
|
||||
candidates and the mode mark and nothing else.
|
||||
|
||||
## 4. Rejected
|
||||
|
||||
**A popup per display, routed by context.** This is what fcitx5 does:
|
||||
`uis_["x11:..."]` and `uis_["wayland:..."]` side by side
|
||||
(`classicui.cpp:80-121`), chosen by the focus group's display name. It would
|
||||
keep XIM alive under XWayland. It costs a channel in `Keyreq`, a `lastdraw`
|
||||
per sink, blanking the loser on every owner change, and `font.c`'s single
|
||||
Pango layout drawn from two processes. It also makes the frontends know about
|
||||
each other: fcitx5 has to drop its X keyboard grab whenever a Wayland client
|
||||
takes focus (`xcb/xcbconnection.cpp:443-460`). fcitx5 needs this because it
|
||||
is a multi-seat, multi-display, multi-user daemon. strans is one user's. If
|
||||
XIM under XWayland is ever wanted, the routing key to use is the one fcitx5
|
||||
uses, and it is about ten lines.
|
||||
|
||||
**A second process for drawing, or a second connection.** See 3.2. The popup
|
||||
surface cannot come from a second connection, and a second process sharing
|
||||
the first buys only the locking rules we currently do not need.
|
||||
|
||||
**A GNOME fallback to the X11 popup.** Machinery for a case that cannot work
|
||||
anyway.
|
||||
|
||||
**Hand-marshalling with `wl_proxy_marshal_flags`** instead of
|
||||
wayland-scanner. Less code, unreadable. The generated files are 1448 lines
|
||||
and none of them are ours to read.
|
||||
|
||||
**Choosing candidates with the mouse.** fcitx5's popup takes clicks, hover,
|
||||
wheel and touch. Ours eats clicks and does nothing, on X11 today; keep it
|
||||
that way.
|
||||
|
||||
## 5. Commit plan
|
||||
|
||||
1. `ibus: SetCursorLocationRelative is a caret too` — one entry in `ictab`.
|
||||
Unrelated to the rest, and wrong today.
|
||||
2. `build: the input-method and virtual-keyboard protocols` — two XML files,
|
||||
the scanner rules, the LICENSES note. No behaviour.
|
||||
3. `wl: the input-method frontend` — activate, deactivate, content type,
|
||||
done and its serial; grab per activate; keys through xkb and `composekey`;
|
||||
commit and preedit; unhandled keys through the virtual keyboard, released
|
||||
on deactivate. Started from `main.c` under `WAYLAND_DISPLAY`. No popup
|
||||
yet, so the X11 popup still owns `drawc` and nothing collides.
|
||||
4. `wl: the candidate popup` — surface, two shm buffers, drawn from `drawc`
|
||||
where the reply arrives. In the same commit, `main.c` stops starting
|
||||
`drawthread` and `ximthread` under Wayland, because that is where the two
|
||||
popups would collide.
|
||||
5. `docs: Wayland` — set nothing; no XIM; no candidates for IBus and GTK
|
||||
module clients.
|
||||
|
||||
Later, separately: key repeat, `preferred_buffer_scale`, reconversion through
|
||||
`surrounding_text`, and a headless live test.
|
||||
|
||||
## 6. Verification
|
||||
|
||||
The toolchain question is already answered. In the container, wayland-scanner
|
||||
output, libwayland-client, xkbcommon and Plan 9 port headers compile together
|
||||
under `9c -Wall -Wextra` with no warnings, and `9l` links them with
|
||||
`-lthread -lbio`:
|
||||
|
||||
```
|
||||
imv2.c 133 imv2.h 955 vkv1.c 78 vkv1.h 282 (generated, not tracked)
|
||||
9c -I/src $(pkg-config --cflags wayland-client xkbcommon) -Wall -Wextra -c wl.c
|
||||
9l -o strans ... -lthread -lbio $(pkg-config --libs wayland-client xkbcommon)
|
||||
```
|
||||
|
||||
`Dockerfile` needs `wayland` added, and `sway` (extra, 1.12) when the live
|
||||
test arrives.
|
||||
|
||||
Unit tests cover only what is pure: the xkb modifier mask and the forwarded
|
||||
key bitmap, in the shape `xim_adapter_test` already uses, compiling `wl.c`
|
||||
into the test and building a keymap from a string, no server. Everything else
|
||||
is checked by running it under sway.
|
||||
|
||||
A headless live test — a compositor, a text-input-v3 client, and a second
|
||||
virtual keyboard to type with — is the size of `ibus_live_test.c`. Decide
|
||||
after commits 3 and 4 work by hand; do not build it first.
|
||||
|
||||
## 7. Known limits
|
||||
|
||||
* A key we eat does not repeat. `zwp_input_method_v2`'s grab has no
|
||||
compositor-side repeat unless the compositor sends `wl_keyboard`'s
|
||||
`repeated` key state through it, which is opportunistic
|
||||
(fcitx5 `waylandimserverv2.cpp:494-503`). Doing it ourselves is a timer
|
||||
over `repeat_info`, twenty lines, later.
|
||||
* Compositor keybindings run before the input method (sway
|
||||
`keyboard.c:563`), so a chord like `Ctrl+N` can be taken from us.
|
||||
* Text pending when the client loses focus may be lost: `deactivate` is the
|
||||
first we hear of it and a `commit_string` afterwards can be dropped.
|
||||
* The protocol gives one input context per seat, so we cannot tell which
|
||||
application we are typing into. We keep no per-application state, so this
|
||||
costs us nothing; fcitx5 tracks the focused window over a separate protocol
|
||||
to work around it.
|
||||
|
||||
## 8. References
|
||||
|
||||
Checked at the versions current on 2026-08-17.
|
||||
|
||||
* wayland-protocols 1.49: `unstable/text-input/text-input-unstable-v3.xml`,
|
||||
`experimental/xx-input-method/xx-input-method-v2.xml`,
|
||||
`experimental/xx-keyboard-filter/xx-keyboard-filter-v1.xml`.
|
||||
* wlroots: `protocol/input-method-unstable-v2.xml`,
|
||||
`protocol/virtual-keyboard-unstable-v1.xml`, the two files to vendor.
|
||||
* sway `sway/input/keyboard.c:419-431`, the virtual keyboard client-identity
|
||||
rule; `:563`, bindings before the grab.
|
||||
* fcitx5 `a31cff7`: `frontend/waylandim/waylandimserverv2.cpp:170` serial,
|
||||
`:186-193` releasing forwarded keys, `:213-218` regrabbing,
|
||||
`:494-503` repeat; `waylandimserverbase.cpp:35-48` commit-as-text;
|
||||
`ui/classic/classicui.cpp:80-121` a UI per display;
|
||||
`ui/classic/waylandshmwindow.cpp:69-113` two buffers;
|
||||
`ui/classic/waylandinputwindow.cpp:158-168` hiding;
|
||||
`modules/wayland/waylandeventreader.cpp` the reader thread;
|
||||
`modules/xcb/xcbconnection.cpp:443-460` the two frontends interfering.
|
||||
* ibus `client/gtk3/ibusimcontext.c:1767`, the relative caret on Wayland.
|
||||
Reference in New Issue
Block a user