mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-22 02:01:55 +07:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8ba57fcf25 | |||
| 126ca37d96 | |||
| e6bd60fbb1 | |||
| a605a3f016 | |||
| ef44adea69 | |||
| 7fdb918cd0 | |||
| 8347cc20dc | |||
| 51a176ec4a | |||
| d618daf6b9 | |||
| 357f9157cc | |||
| c4a759e620 | |||
| f369a0f810 | |||
| 71251a7003 | |||
| 2415346caa | |||
| 3f2b7e63ba |
Generated
+72
-6
@@ -385,6 +385,15 @@ version = "2.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block2"
|
||||
version = "0.5.1"
|
||||
@@ -747,6 +756,15 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.4.2"
|
||||
@@ -781,6 +799,16 @@ version = "0.8.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "csscolorparser"
|
||||
version = "0.7.0"
|
||||
@@ -802,6 +830,16 @@ version = "0.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"crypto-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "directories"
|
||||
version = "6.0.0"
|
||||
@@ -1256,6 +1294,16 @@ dependencies = [
|
||||
"windows",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gethostname"
|
||||
version = "0.4.3"
|
||||
@@ -2210,7 +2258,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "niri"
|
||||
version = "25.5.0"
|
||||
version = "25.5.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"approx 0.5.1",
|
||||
@@ -2267,7 +2315,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "niri-config"
|
||||
version = "25.5.0"
|
||||
version = "25.5.1"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
"csscolorparser",
|
||||
@@ -2284,7 +2332,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "niri-ipc"
|
||||
version = "25.5.0"
|
||||
version = "25.5.1"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"schemars",
|
||||
@@ -2294,7 +2342,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "niri-visual-tests"
|
||||
version = "25.5.0"
|
||||
version = "25.5.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"gtk4",
|
||||
@@ -3365,6 +3413,17 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.10.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sharded-slab"
|
||||
version = "0.1.7"
|
||||
@@ -3431,7 +3490,7 @@ checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c"
|
||||
[[package]]
|
||||
name = "smithay"
|
||||
version = "0.6.0"
|
||||
source = "git+https://github.com/Smithay/smithay.git#c1f13a6b9605c9f7009122a7b2b34f210255dac3"
|
||||
source = "git+https://github.com/Smithay/smithay.git#ede27079f45eeb7c21796e22f3bc25b741b024ea"
|
||||
dependencies = [
|
||||
"aliasable",
|
||||
"appendlist",
|
||||
@@ -3458,6 +3517,7 @@ dependencies = [
|
||||
"profiling",
|
||||
"rand 0.9.1",
|
||||
"rustix 0.38.44",
|
||||
"sha2",
|
||||
"smallvec",
|
||||
"tempfile",
|
||||
"thiserror 2.0.12",
|
||||
@@ -3504,7 +3564,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "smithay-drm-extras"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/Smithay/smithay.git#c1f13a6b9605c9f7009122a7b2b34f210255dac3"
|
||||
source = "git+https://github.com/Smithay/smithay.git#ede27079f45eeb7c21796e22f3bc25b741b024ea"
|
||||
dependencies = [
|
||||
"drm",
|
||||
"libdisplay-info",
|
||||
@@ -3852,6 +3912,12 @@ dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
|
||||
|
||||
[[package]]
|
||||
name = "udev"
|
||||
version = "0.9.3"
|
||||
|
||||
+3
-3
@@ -6,7 +6,7 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "25.5.0"
|
||||
version = "25.5.1"
|
||||
description = "A scrollable-tiling Wayland compositor"
|
||||
authors = ["Ivan Molodetskikh <yalterz@gmail.com>"]
|
||||
license = "GPL-3.0-or-later"
|
||||
@@ -71,8 +71,8 @@ keyframe = { version = "1.1.1", default-features = false }
|
||||
libc = "0.2.172"
|
||||
libdisplay-info = "0.2.2"
|
||||
log = { version = "0.4.27", features = ["max_level_trace", "release_max_level_debug"] }
|
||||
niri-config = { version = "25.5.0", path = "niri-config" }
|
||||
niri-ipc = { version = "25.5.0", path = "niri-ipc", features = ["clap"] }
|
||||
niri-config = { version = "25.5.1", path = "niri-config" }
|
||||
niri-ipc = { version = "25.5.1", path = "niri-ipc", features = ["clap"] }
|
||||
ordered-float = "5.0.0"
|
||||
pango = { version = "0.20.10", features = ["v1_44"] }
|
||||
pangocairo = "0.20.10"
|
||||
|
||||
@@ -12,7 +12,7 @@ bitflags.workspace = true
|
||||
csscolorparser = "0.7.0"
|
||||
knuffel = "3.2.0"
|
||||
miette = { version = "5.10.0", features = ["fancy-no-backtrace"] }
|
||||
niri-ipc = { version = "25.5.0", path = "../niri-ipc" }
|
||||
niri-ipc = { version = "25.5.1", path = "../niri-ipc" }
|
||||
regex = "1.11.1"
|
||||
smithay = { workspace = true, features = ["backend_libinput"] }
|
||||
tracing.workspace = true
|
||||
|
||||
@@ -1888,11 +1888,11 @@ pub enum Action {
|
||||
OpenOverview,
|
||||
CloseOverview,
|
||||
#[knuffel(skip)]
|
||||
ToggleUrgent(u64),
|
||||
ToggleWindowUrgent(u64),
|
||||
#[knuffel(skip)]
|
||||
SetUrgent(u64),
|
||||
SetWindowUrgent(u64),
|
||||
#[knuffel(skip)]
|
||||
UnsetUrgent(u64),
|
||||
UnsetWindowUrgent(u64),
|
||||
}
|
||||
|
||||
impl From<niri_ipc::Action> for Action {
|
||||
@@ -2165,9 +2165,9 @@ impl From<niri_ipc::Action> for Action {
|
||||
niri_ipc::Action::ToggleOverview {} => Self::ToggleOverview,
|
||||
niri_ipc::Action::OpenOverview {} => Self::OpenOverview,
|
||||
niri_ipc::Action::CloseOverview {} => Self::CloseOverview,
|
||||
niri_ipc::Action::ToggleUrgent { id } => Self::ToggleUrgent(id),
|
||||
niri_ipc::Action::SetUrgent { id } => Self::SetUrgent(id),
|
||||
niri_ipc::Action::UnsetUrgent { id } => Self::UnsetUrgent(id),
|
||||
niri_ipc::Action::ToggleWindowUrgent { id } => Self::ToggleWindowUrgent(id),
|
||||
niri_ipc::Action::SetWindowUrgent { id } => Self::SetWindowUrgent(id),
|
||||
niri_ipc::Action::UnsetWindowUrgent { id } => Self::UnsetWindowUrgent(id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -12,5 +12,5 @@ Use an exact version requirement to avoid breaking changes:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
niri-ipc = "=25.5.0"
|
||||
niri-ipc = "=25.5.1"
|
||||
```
|
||||
|
||||
+4
-4
@@ -41,7 +41,7 @@
|
||||
//!
|
||||
//! ```toml
|
||||
//! [dependencies]
|
||||
//! niri-ipc = "=25.5.0"
|
||||
//! niri-ipc = "=25.5.1"
|
||||
//! ```
|
||||
//!
|
||||
//! ## Features
|
||||
@@ -823,19 +823,19 @@ pub enum Action {
|
||||
/// Close the Overview.
|
||||
CloseOverview {},
|
||||
/// Toggle urgent status of a window.
|
||||
ToggleUrgent {
|
||||
ToggleWindowUrgent {
|
||||
/// Id of the window to toggle urgent.
|
||||
#[cfg_attr(feature = "clap", arg(long))]
|
||||
id: u64,
|
||||
},
|
||||
/// Set urgent status of a window.
|
||||
SetUrgent {
|
||||
SetWindowUrgent {
|
||||
/// Id of the window to set urgent.
|
||||
#[cfg_attr(feature = "clap", arg(long))]
|
||||
id: u64,
|
||||
},
|
||||
/// Unset urgent status of a window.
|
||||
UnsetUrgent {
|
||||
UnsetWindowUrgent {
|
||||
/// Id of the window to unset urgent.
|
||||
#[cfg_attr(feature = "clap", arg(long))]
|
||||
id: u64,
|
||||
|
||||
@@ -11,8 +11,8 @@ repository.workspace = true
|
||||
adw = { version = "0.7.2", package = "libadwaita", features = ["v1_4"] }
|
||||
anyhow.workspace = true
|
||||
gtk = { version = "0.9.6", package = "gtk4", features = ["v4_12"] }
|
||||
niri = { version = "25.5.0", path = ".." }
|
||||
niri-config = { version = "25.5.0", path = "../niri-config" }
|
||||
niri = { version = "25.5.1", path = ".." }
|
||||
niri-config = { version = "25.5.1", path = "../niri-config" }
|
||||
smithay.workspace = true
|
||||
tracing.workspace = true
|
||||
tracing-subscriber.workspace = true
|
||||
|
||||
@@ -174,8 +174,24 @@ impl State {
|
||||
self.niri.layer_shell_on_demand_focus = Some(layer.clone());
|
||||
}
|
||||
} else {
|
||||
self.niri.mapped_layer_surfaces.remove(layer);
|
||||
let was_mapped = self.niri.mapped_layer_surfaces.remove(layer).is_some();
|
||||
self.niri.unmapped_layer_surfaces.insert(surface.clone());
|
||||
|
||||
// After layer surface unmaps it has to perform the initial commit-configure
|
||||
// sequence again. This is a workaround until Smithay properly resets
|
||||
// initial_configure_sent upon the surface unmapping itself as it does for
|
||||
// toplevels.
|
||||
if was_mapped {
|
||||
with_states(surface, |states| {
|
||||
let mut data = states
|
||||
.data_map
|
||||
.get::<LayerSurfaceData>()
|
||||
.unwrap()
|
||||
.lock()
|
||||
.unwrap();
|
||||
data.initial_configure_sent = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let scale = output.current_scale();
|
||||
|
||||
@@ -334,9 +334,10 @@ impl XdgShellHandler for State {
|
||||
// higher input priority.
|
||||
|
||||
if layers.layers_on(Layer::Overlay).any(|l| {
|
||||
l.cached_state().keyboard_interactivity
|
||||
(l.cached_state().keyboard_interactivity
|
||||
== wlr_layer::KeyboardInteractivity::Exclusive
|
||||
|| Some(l) == self.niri.layer_shell_on_demand_focus.as_ref()
|
||||
|| Some(l) == self.niri.layer_shell_on_demand_focus.as_ref())
|
||||
&& self.niri.mapped_layer_surfaces.contains_key(l)
|
||||
}) {
|
||||
trace!("ignoring toplevel popup grab because the overlay layer has focus");
|
||||
let _ = PopupManager::dismiss_popup(&root, &popup);
|
||||
@@ -346,9 +347,10 @@ impl XdgShellHandler for State {
|
||||
let mon = self.niri.layout.monitor_for_output(output).unwrap();
|
||||
if !mon.render_above_top_layer()
|
||||
&& layers.layers_on(Layer::Top).any(|l| {
|
||||
l.cached_state().keyboard_interactivity
|
||||
(l.cached_state().keyboard_interactivity
|
||||
== wlr_layer::KeyboardInteractivity::Exclusive
|
||||
|| Some(l) == self.niri.layer_shell_on_demand_focus.as_ref()
|
||||
|| Some(l) == self.niri.layer_shell_on_demand_focus.as_ref())
|
||||
&& self.niri.mapped_layer_surfaces.contains_key(l)
|
||||
})
|
||||
{
|
||||
trace!("ignoring toplevel popup grab because the top layer has focus");
|
||||
|
||||
+17
-4
@@ -472,6 +472,12 @@ impl State {
|
||||
}
|
||||
|
||||
fn hide_cursor_if_needed(&mut self) {
|
||||
// If the pointer is already invisible, don't reset it back to Hidden causing one frame
|
||||
// of hover.
|
||||
if !self.niri.pointer_visibility.is_visible() {
|
||||
return;
|
||||
}
|
||||
|
||||
if !self.niri.config.borrow().cursor.hide_when_typing {
|
||||
return;
|
||||
}
|
||||
@@ -1976,7 +1982,7 @@ impl State {
|
||||
self.niri.queue_redraw_all();
|
||||
}
|
||||
}
|
||||
Action::ToggleUrgent(id) => {
|
||||
Action::ToggleWindowUrgent(id) => {
|
||||
let window = self
|
||||
.niri
|
||||
.layout
|
||||
@@ -1986,8 +1992,9 @@ impl State {
|
||||
let urgent = window.is_urgent();
|
||||
window.set_urgent(!urgent);
|
||||
}
|
||||
self.niri.queue_redraw_all();
|
||||
}
|
||||
Action::SetUrgent(id) => {
|
||||
Action::SetWindowUrgent(id) => {
|
||||
let window = self
|
||||
.niri
|
||||
.layout
|
||||
@@ -1996,8 +2003,9 @@ impl State {
|
||||
if let Some(window) = window {
|
||||
window.set_urgent(true);
|
||||
}
|
||||
self.niri.queue_redraw_all();
|
||||
}
|
||||
Action::UnsetUrgent(id) => {
|
||||
Action::UnsetWindowUrgent(id) => {
|
||||
let window = self
|
||||
.niri
|
||||
.layout
|
||||
@@ -2006,6 +2014,7 @@ impl State {
|
||||
if let Some(window) = window {
|
||||
window.set_urgent(false);
|
||||
}
|
||||
self.niri.queue_redraw_all();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3598,7 +3607,11 @@ impl State {
|
||||
let mods = modifiers_from_state(mods);
|
||||
let mod_down = mods.contains(mod_key.to_modifiers());
|
||||
|
||||
if self.niri.layout.is_overview_open() && !mod_down && under.layer.is_none() {
|
||||
if self.niri.layout.is_overview_open()
|
||||
&& !mod_down
|
||||
&& under.layer.is_none()
|
||||
&& under.output.is_some()
|
||||
{
|
||||
let (output, pos_within_output) = self.niri.output_under(pos).unwrap();
|
||||
let output = output.clone();
|
||||
|
||||
|
||||
+33
-5
@@ -740,7 +740,21 @@ impl State {
|
||||
}
|
||||
|
||||
pub fn move_cursor(&mut self, location: Point<f64, Logical>) {
|
||||
let under = self.niri.contents_under(location);
|
||||
let mut under = match self.niri.pointer_visibility {
|
||||
PointerVisibility::Disabled => PointContents::default(),
|
||||
_ => self.niri.contents_under(location),
|
||||
};
|
||||
|
||||
// Disable the hidden pointer if the contents underneath have changed.
|
||||
if !self.niri.pointer_visibility.is_visible() && self.niri.pointer_contents != under {
|
||||
self.niri.pointer_visibility = PointerVisibility::Disabled;
|
||||
|
||||
// When setting PointerVisibility::Hidden together with pointer contents changing,
|
||||
// we can change straight to nothing to avoid one frame of hover. Notably, this can
|
||||
// be triggered through warp-mouse-to-focus combined with hide-when-typing.
|
||||
under = PointContents::default();
|
||||
}
|
||||
|
||||
self.niri.pointer_contents.clone_from(&under);
|
||||
|
||||
let pointer = &self.niri.seat.get_pointer().unwrap();
|
||||
@@ -929,7 +943,7 @@ impl State {
|
||||
|
||||
let pointer = &self.niri.seat.get_pointer().unwrap();
|
||||
let location = pointer.current_location();
|
||||
let under = match self.niri.pointer_visibility {
|
||||
let mut under = match self.niri.pointer_visibility {
|
||||
PointerVisibility::Disabled => PointContents::default(),
|
||||
_ => self.niri.contents_under(location),
|
||||
};
|
||||
@@ -943,6 +957,14 @@ impl State {
|
||||
// Disable the hidden pointer if the contents underneath have changed.
|
||||
if !self.niri.pointer_visibility.is_visible() {
|
||||
self.niri.pointer_visibility = PointerVisibility::Disabled;
|
||||
|
||||
// When setting PointerVisibility::Hidden together with pointer contents changing,
|
||||
// we can change straight to nothing to avoid one frame of hover. Notably, this can
|
||||
// be triggered through warp-mouse-to-focus combined with hide-when-typing.
|
||||
under = PointContents::default();
|
||||
if self.niri.pointer_contents == under {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
self.niri.pointer_contents.clone_from(&under);
|
||||
@@ -996,12 +1018,13 @@ impl State {
|
||||
&& surface.cached_state().keyboard_interactivity
|
||||
== wlr_layer::KeyboardInteractivity::OnDemand;
|
||||
|
||||
// Check if it moved to the overview backdrop.
|
||||
if let Some(mapped) = self.niri.mapped_layer_surfaces.get(surface) {
|
||||
// Check if it moved to the overview backdrop.
|
||||
if mapped.place_within_backdrop() {
|
||||
good = false;
|
||||
}
|
||||
} else {
|
||||
// The layer surface is alive but it got unmapped.
|
||||
good = false;
|
||||
}
|
||||
|
||||
@@ -5985,8 +6008,13 @@ impl Niri {
|
||||
.event_loop
|
||||
.insert_source(timer, move |_, _, state| {
|
||||
state.niri.pointer_inactivity_timer = None;
|
||||
state.niri.pointer_visibility = PointerVisibility::Hidden;
|
||||
state.niri.queue_redraw_all();
|
||||
|
||||
// If the pointer is already invisible, don't reset it back to Hidden causing one
|
||||
// frame of hover.
|
||||
if state.niri.pointer_visibility.is_visible() {
|
||||
state.niri.pointer_visibility = PointerVisibility::Hidden;
|
||||
state.niri.queue_redraw_all();
|
||||
}
|
||||
|
||||
TimeoutAction::Drop
|
||||
})
|
||||
|
||||
@@ -175,8 +175,6 @@ Set the `numlock` flag to turn on Num Lock automatically at startup.
|
||||
|
||||
You might want to disable (comment out) `numlock` if you're using a laptop with a keyboard that overlays Num Lock keys on top of regular keys.
|
||||
|
||||
Note that there's a [known issue](https://github.com/YaLTeR/niri/issues/1501) with this setting: Num Lock only turns on after you press some modifier key (Super, Alt, etc.).
|
||||
|
||||
```kdl
|
||||
input {
|
||||
keyboard {
|
||||
|
||||
@@ -28,6 +28,22 @@ To do that, put files into the correct directories according to this table.
|
||||
|
||||
Doing this will make niri appear in GDM and other display managers.
|
||||
|
||||
### Running tests
|
||||
|
||||
A bulk of our tests spawn niri compositor instances and test Wayland clients.
|
||||
This does not require a graphical session, however due to test parallelism, it can run into file descriptor limits on high core count systems.
|
||||
|
||||
If you run into this problem, you may need to limit not just the Rust test harness thread count, but also the Rayon thread count, since some niri tests use internal Rayon threading:
|
||||
|
||||
```
|
||||
$ export RAYON_NUM_THREADS=2
|
||||
...proceed to run cargo test, perhaps with --test-threads=2
|
||||
```
|
||||
|
||||
Don't forget to exclude the development-only `niri-visual-tests` crate when running tests.
|
||||
|
||||
You may also want to set the `RUN_SLOW_TESTS=1` environment variable to run the slower tests.
|
||||
|
||||
### Version string
|
||||
|
||||
The niri version string includes its version and commit hash:
|
||||
|
||||
Reference in New Issue
Block a user