mirror of
https://github.com/lhns/steam-frame-nix.git
synced 2026-10-06 04:04:16 +02:00
launcher-menu: detect missing icon fallbacks on switch (iconFallbacks.enable/extra)
This commit is contained in:
1 parent
cfa9ca7e15
commit
8d7094916b
4 files changed
+229
-45
No files matched your search
@@ -195,7 +195,8 @@ menu list every desktop entry, including terminals such as Konsole.
|
|||||||
| `steamFrame.launcherMenu.grid.enable` | bool | `false` | Show the "+" menu's programs as a grid of tiles (large icon, name below) instead of a list. |
|
| `steamFrame.launcherMenu.grid.enable` | bool | `false` | Show the "+" menu's programs as a grid of tiles (large icon, name below) instead of a list. |
|
||||||
| `steamFrame.launcherMenu.grid.columns` | int, 1-8 | `4` | Tiles per row (popup is 300 px wide: 3 ≈ 92 px, 4 ≈ 68 px, 5 ≈ 53 px tiles). |
|
| `steamFrame.launcherMenu.grid.columns` | int, 1-8 | `4` | Tiles per row (popup is 300 px wide: 3 ≈ 92 px, 4 ≈ 68 px, 5 ≈ 53 px tiles). |
|
||||||
| `steamFrame.launcherMenu.grid.maxRows` | null or positive int | `null` | Rows visible at once, the rest scrolls. `null`: fill up to the menu's max height (600 px). |
|
| `steamFrame.launcherMenu.grid.maxRows` | null or positive int | `null` | Rows visible at once, the rest scrolls. `null`: fill up to the menu's max height (600 px). |
|
||||||
| `steamFrame.launcherMenu.iconFallbacks` | list of str | `[ "utilities-terminal" "preferences-system" ]` | Icon names installed as hicolor copies of Breeze app icons, so the "+" menu shows them (Konsole, KDE System Settings). `[ ]`: none. Takes effect after a Steam restart. |
|
| `steamFrame.launcherMenu.iconFallbacks.enable` | bool | `true` | On every switch, link hicolor fallbacks (Breeze app icons) for desktop entries whose icon Steam can't find, so the "+" menu shows them (SteamOS: Konsole, KDE System Settings). `false` removes the links. See [Icon fallbacks](#icon-fallbacks-launchermenuiconfallbacks). |
|
||||||
|
| `steamFrame.launcherMenu.iconFallbacks.extra` | list of str | `[ ]` | Further icon names to provide even if no desktop entry the scan sees uses them. |
|
||||||
| `steamFrame.dashboard.windowMaxScale` | null or number | `null` | Largest resize-handle scale of SteamVR dashboard windows, relative to their default size. `null`: stock (2). See [Dashboard windows](#dashboard-windows-dashboard). |
|
| `steamFrame.dashboard.windowMaxScale` | null or number | `null` | Largest resize-handle scale of SteamVR dashboard windows, relative to their default size. `null`: stock (2). See [Dashboard windows](#dashboard-windows-dashboard). |
|
||||||
| `steamFrame.dashboard.windowDistance.{world,theater,dashboard}.{min,max}` | null or number (m) | `null` | How close / far grabbed windows can be pulled in / pushed back. `null`: stock (world 0.25-5, theater 1-6, dashboard 0.3-4 m). |
|
| `steamFrame.dashboard.windowDistance.{world,theater,dashboard}.{min,max}` | null or number (m) | `null` | How close / far grabbed windows can be pulled in / pushed back. `null`: stock (world 0.25-5, theater 1-6, dashboard 0.3-4 m). |
|
||||||
| `steamFrame.dashboard.steamCloseButton.enable` | bool | `false` | Close (X) button on the dashboard's Steam window: switches to the previous window, or leaves just the dashboard bar. See [Steam close button](#steam-close-button-dashboardsteamclosebuttonenable). |
|
| `steamFrame.dashboard.steamCloseButton.enable` | bool | `false` | Close (X) button on the dashboard's Steam window: switches to the previous window, or leaves just the dashboard bar. See [Steam close button](#steam-close-button-dashboardsteamclosebuttonenable). |
|
||||||
@@ -551,19 +552,30 @@ desktop's icon theme. Konsole (`Icon=utilities-terminal`) and KDE System
|
|||||||
Settings (`Icon=preferences-system`), which SteamOS ships, have their icons
|
Settings (`Icon=preferences-system`), which SteamOS ships, have their icons
|
||||||
only in Breeze, so they show up without an icon in the "+" menu.
|
only in Breeze, so they show up without an icon in the "+" menu.
|
||||||
|
|
||||||
**Fix:** for each name in `iconFallbacks` (default: those two), the largest
|
**Fix:** on every `home-manager switch`, a small activation script looks
|
||||||
Breeze app icon from nixpkgs' `kdePackages.breeze-icons` is installed as
|
through the desktop entries Steam sees (`applications/` in
|
||||||
`~/.nix-profile/share/icons/hicolor/scalable/apps/<name>.svg`
|
`~/.local/share` and Steam's `XDG_DATA_DIRS`; an entry shadowed by one with
|
||||||
(`~/.nix-profile/share` is on Steam's `XDG_DATA_DIRS`); desktop entries are
|
the same desktop-file ID, and `Hidden`/`NoDisplay` entries, are skipped).
|
||||||
left alone. Add names for other programs that lack an icon (the `Icon=`
|
For each `Icon=` name that no hicolor theme dir (or `pixmaps`) has but
|
||||||
value of their desktop entry), e.g.
|
nixpkgs' `kdePackages.breeze-icons` app icons do, it links the largest
|
||||||
`iconFallbacks = [ "utilities-terminal" "preferences-system" "system-file-manager" ];`;
|
Breeze SVG as `~/.local/share/icons/hicolor/scalable/apps/<name>.svg`.
|
||||||
`[ ]` installs nothing. A name Breeze has no app icon for fails the build
|
Desktop entries are left alone. `iconFallbacks.extra` adds names to
|
||||||
with an error naming it.
|
provide even if no scanned entry uses them (a name Breeze lacks is
|
||||||
|
reported and skipped). The links made are listed in
|
||||||
|
`~/.local/state/steam-frame-nix/icon-fallbacks`; links no longer needed are
|
||||||
|
removed, and `iconFallbacks.enable = false` removes them all. Only those
|
||||||
|
links (still pointing into a breeze-icons store path) are ever removed;
|
||||||
|
other files in the icon dir are never touched. Programs installed later get
|
||||||
|
their fallback on the next switch.
|
||||||
|
|
||||||
**Caveat:** Steam caches the icon lookup (GTK icon cache) for its lifetime,
|
`iconFallbacks` used to be a list of icon names; setting a list now fails
|
||||||
and files in the Nix store all have the same 1970 mtime, so new icons show
|
evaluation with a message: use `iconFallbacks.extra` for additional names,
|
||||||
up only after Steam is restarted (e.g. a reboot), not on the next switch.
|
`iconFallbacks.enable = false` for what `[ ]` meant.
|
||||||
|
|
||||||
|
A running Steam picks up the change without a restart: it caches the icon
|
||||||
|
theme (GTK) and rescans it only when a theme directory's mtime changes, so
|
||||||
|
the script bumps the mtime of `~/.local/share/icons/hicolor` whenever it
|
||||||
|
adds or removes links.
|
||||||
|
|
||||||
### Dashboard windows (`dashboard.*`)
|
### Dashboard windows (`dashboard.*`)
|
||||||
|
|
||||||
|
|||||||
+76
-32
@@ -23,32 +23,23 @@
|
|||||||
# iconFallbacks is not a patch: Steam's scan of host programs resolves a
|
# iconFallbacks is not a patch: Steam's scan of host programs resolves a
|
||||||
# desktop entry's Icon= name only in the hicolor icon theme (and pixmaps),
|
# desktop entry's Icon= name only in the hicolor icon theme (and pixmaps),
|
||||||
# so programs whose icon exists only in the desktop's Breeze theme (SteamOS'
|
# so programs whose icon exists only in the desktop's Breeze theme (SteamOS'
|
||||||
# Konsole and KDE System Settings) have no icon in the menu. Hicolor copies
|
# Konsole and KDE System Settings) have no icon in the menu. On every switch,
|
||||||
# from nixpkgs' Breeze are installed into the profile (~/.nix-profile/share
|
# launcher-menu/icon-fallbacks.sh looks through the desktop entries Steam
|
||||||
# is on Steam's XDG_DATA_DIRS).
|
# sees and links the missing icons that nixpkgs' Breeze has into
|
||||||
|
# ~/.local/share/icons/hicolor (see the script). Until 2026-09 iconFallbacks
|
||||||
|
# was a list of names; setting a list now fails with a message pointing to
|
||||||
|
# enable/extra.
|
||||||
{ config, lib, pkgs, ... }:
|
{ config, lib, pkgs, ... }:
|
||||||
let
|
let
|
||||||
cfg = config.steamFrame.launcherMenu;
|
cfg = config.steamFrame.launcherMenu;
|
||||||
inherit (import ./lib { inherit pkgs; }) mkPatch;
|
inherit (import ./lib { inherit pkgs; }) mkPatch;
|
||||||
sharedJSContext = { title = "SharedJSContext"; };
|
sharedJSContext = { title = "SharedJSContext"; };
|
||||||
|
|
||||||
# Largest Breeze app icon (all are SVG) as hicolor/scalable/apps/<name>.svg.
|
iconFallbacks = pkgs.writeShellApplication {
|
||||||
breezeApps = "${pkgs.kdePackages.breeze-icons}/share/icons/breeze/apps";
|
name = "steam-frame-icon-fallbacks";
|
||||||
iconFallbacks = pkgs.runCommand "launcher-menu-icon-fallbacks" { } ''
|
runtimeInputs = [ pkgs.coreutils pkgs.findutils pkgs.gawk ];
|
||||||
dir=$out/share/icons/hicolor/scalable/apps
|
text = builtins.readFile ./launcher-menu/icon-fallbacks.sh;
|
||||||
mkdir -p $dir
|
};
|
||||||
for name in ${lib.escapeShellArgs cfg.iconFallbacks}; do
|
|
||||||
src=
|
|
||||||
for size in 64 48 32 24 22 16; do
|
|
||||||
if [ -e "${breezeApps}/$size/$name.svg" ]; then src="${breezeApps}/$size/$name.svg"; break; fi
|
|
||||||
done
|
|
||||||
if [ -z "$src" ]; then
|
|
||||||
echo "steamFrame.launcherMenu.iconFallbacks: no Breeze app icon \"$name\" (${breezeApps}/<size>/$name.svg)" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
cp -L "$src" "$dir/$name.svg"
|
|
||||||
done
|
|
||||||
'';
|
|
||||||
in {
|
in {
|
||||||
imports = [ ./steam-ui-patches.nix ];
|
imports = [ ./steam-ui-patches.nix ];
|
||||||
|
|
||||||
@@ -122,23 +113,76 @@ in {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
iconFallbacks = lib.mkOption {
|
iconFallbacks = lib.mkOption {
|
||||||
type = lib.types.listOf (lib.types.strMatching "[A-Za-z0-9._+-]+");
|
default = { };
|
||||||
default = [ "utilities-terminal" "preferences-system" ];
|
|
||||||
example = [ "utilities-terminal" "preferences-system" "system-file-manager" ];
|
|
||||||
description = ''
|
description = ''
|
||||||
Icon names (Icon= of desktop entries) installed as hicolor icons,
|
Hicolor fallbacks for program icons only the desktop's Breeze theme
|
||||||
copied from nixpkgs' Breeze app icons, so the "+" menu shows them:
|
has, so the "+" menu shows them: Steam resolves program icons only in
|
||||||
Steam resolves program icons only in the hicolor theme, not in the
|
the hicolor theme.
|
||||||
desktop's Breeze theme. The default covers the entries SteamOS ships
|
|
||||||
whose icons exist only in Breeze: Konsole (utilities-terminal) and
|
|
||||||
KDE System Settings (preferences-system). A name Breeze has no app
|
|
||||||
icon for fails the build, naming it. [] installs nothing. Steam
|
|
||||||
picks up new icons only after a restart.
|
|
||||||
'';
|
'';
|
||||||
|
# The option used to be a list of icon names: keep a list from being
|
||||||
|
# silently misread and fail with a message instead (see the assertion).
|
||||||
|
type = lib.types.coercedTo (lib.types.listOf lib.types.str)
|
||||||
|
(names: { legacyList = names; })
|
||||||
|
(lib.types.submodule {
|
||||||
|
options = {
|
||||||
|
enable = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = true;
|
||||||
|
description = ''
|
||||||
|
On every switch, look through the desktop entries Steam sees
|
||||||
|
(XDG data dirs, shadowed and Hidden/NoDisplay entries skipped)
|
||||||
|
and, for each Icon= name no hicolor theme dir (or pixmaps)
|
||||||
|
has but nixpkgs' Breeze app icons do (SteamOS: Konsole's
|
||||||
|
utilities-terminal, KDE System Settings' preferences-system),
|
||||||
|
link the Breeze SVG as
|
||||||
|
~/.local/share/icons/hicolor/scalable/apps/<name>.svg. Links
|
||||||
|
no longer needed are removed; only links made by this option
|
||||||
|
(listed in ~/.local/state/steam-frame-nix/icon-fallbacks) are
|
||||||
|
ever touched. false removes them all.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
extra = lib.mkOption {
|
||||||
|
type = lib.types.listOf (lib.types.strMatching "[A-Za-z0-9._+-]+");
|
||||||
|
default = [ ];
|
||||||
|
example = [ "system-file-manager" ];
|
||||||
|
description = ''
|
||||||
|
Icon names to provide even if no desktop entry the scan sees
|
||||||
|
uses them (still only if hicolor lacks them). A name Breeze
|
||||||
|
has no app icon for is reported on switch and skipped.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
legacyList = lib.mkOption {
|
||||||
|
type = lib.types.nullOr (lib.types.listOf lib.types.str);
|
||||||
|
default = null;
|
||||||
|
internal = true;
|
||||||
|
visible = false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
});
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config.home.packages = lib.optional (cfg.iconFallbacks != [ ]) iconFallbacks;
|
config.assertions = [ {
|
||||||
|
assertion = cfg.iconFallbacks.legacyList == null;
|
||||||
|
message = ''
|
||||||
|
steamFrame.launcherMenu.iconFallbacks is no longer a list of icon names
|
||||||
|
(set to ${builtins.toJSON cfg.iconFallbacks.legacyList}). Use
|
||||||
|
iconFallbacks.enable = true; # default: utilities-terminal, preferences-system
|
||||||
|
iconFallbacks.extra = [ ... ]; # further names
|
||||||
|
or iconFallbacks.enable = false; for none (what [ ] used to mean).
|
||||||
|
'';
|
||||||
|
} ];
|
||||||
|
|
||||||
|
# After installPackages, so the new profile's desktop entries and icons count.
|
||||||
|
# Disabled, the script removes its links (no BREEZE_APPS).
|
||||||
|
config.home.activation.steamFrameIconFallbacks =
|
||||||
|
lib.hm.dag.entryAfter [ "writeBoundary" "installPackages" ] (
|
||||||
|
if cfg.iconFallbacks.enable then ''
|
||||||
|
run env BREEZE_APPS=${pkgs.kdePackages.breeze-icons}/share/icons/breeze/apps \
|
||||||
|
${lib.getExe iconFallbacks} ${lib.escapeShellArgs cfg.iconFallbacks.extra}
|
||||||
|
'' else ''
|
||||||
|
run ${lib.getExe iconFallbacks}
|
||||||
|
'');
|
||||||
|
|
||||||
config.steamFrame.uiPatches.patches =
|
config.steamFrame.uiPatches.patches =
|
||||||
lib.optional cfg.sort {
|
lib.optional cfg.sort {
|
||||||
|
|||||||
@@ -0,0 +1,125 @@
|
|||||||
|
# Hicolor fallbacks for desktop entry icons that only Breeze has.
|
||||||
|
#
|
||||||
|
# Usage: steam-frame-icon-fallbacks [EXTRA_NAME...]
|
||||||
|
# BREEZE_APPS=<breeze-icons>/share/icons/breeze/apps: link the Icon= names
|
||||||
|
# of the desktop entries Steam sees (plus EXTRA_NAMEs) that no hicolor
|
||||||
|
# theme dir has but Breeze does, as
|
||||||
|
# $XDG_DATA_HOME/icons/hicolor/scalable/apps/<name>.svg -> Breeze's SVG.
|
||||||
|
# BREEZE_APPS unset: remove all links made before (disabled).
|
||||||
|
# Links made are listed in a manifest; only those, and only while they still
|
||||||
|
# point into a breeze-icons store path, are ever removed. Nothing else in the
|
||||||
|
# icon dir is touched.
|
||||||
|
|
||||||
|
data_home=${XDG_DATA_HOME:-$HOME/.local/share}
|
||||||
|
dest=$data_home/icons/hicolor/scalable/apps
|
||||||
|
manifest=${XDG_STATE_HOME:-$HOME/.local/state}/steam-frame-nix/icon-fallbacks
|
||||||
|
# Steam's data dirs (XDG_DATA_HOME, then its XDG_DATA_DIRS on SteamOS), not
|
||||||
|
# the caller's: switches usually run from the nested desktop session.
|
||||||
|
data_dirs=(
|
||||||
|
"$data_home"
|
||||||
|
"$HOME/.local/share/flatpak/exports/share"
|
||||||
|
/var/lib/flatpak/exports/share
|
||||||
|
/usr/local/share
|
||||||
|
/usr/share
|
||||||
|
"$HOME/.nix-profile/share"
|
||||||
|
/nix/var/nix/profiles/default/share
|
||||||
|
)
|
||||||
|
shopt -s nullglob
|
||||||
|
|
||||||
|
ours() { [[ -L $1 && $(readlink "$1") == /nix/store/*-breeze-icons-*/* ]]; }
|
||||||
|
|
||||||
|
# Does Steam already find <name> (hicolor in any data dir, or pixmaps)?
|
||||||
|
# Our own links don't count.
|
||||||
|
resolves() {
|
||||||
|
local d f
|
||||||
|
for d in "$HOME/.icons" "${data_dirs[@]/%//icons}"; do
|
||||||
|
for f in "$d"/hicolor/*/*/"$1".{png,svg,xpm}; do
|
||||||
|
ours "$f" || return 0
|
||||||
|
done
|
||||||
|
done
|
||||||
|
for d in "${data_dirs[@]}"; do
|
||||||
|
for f in "$d"/pixmaps/"$1".{png,svg,xpm}; do [[ ! -e $f ]] || return 0; done
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Largest Breeze app icon for <name> (Breeze's app icons are all SVG).
|
||||||
|
breeze() {
|
||||||
|
local size
|
||||||
|
for size in 64 48 32 24 22 16; do
|
||||||
|
if [[ -e $BREEZE_APPS/$size/$1.svg ]]; then echo "$BREEZE_APPS/$size/$1.svg"; return; fi
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Icon= of a desktop entry, unless it is Hidden/NoDisplay or not a plain icon
|
||||||
|
# name (e.g. an absolute path).
|
||||||
|
entry_icon() {
|
||||||
|
awk -F= '
|
||||||
|
/^\[/ { group = ($0 ~ /^\[Desktop Entry\][[:space:]]*$/); next }
|
||||||
|
!group { next }
|
||||||
|
{ key = $1; sub(/[[:space:]]+$/, "", key); val = substr($0, index($0, "=") + 1)
|
||||||
|
gsub(/^[[:space:]]+|[[:space:]\r]+$/, "", val) }
|
||||||
|
(key == "Hidden" || key == "NoDisplay") && val == "true" { skip = 1 }
|
||||||
|
key == "Icon" { icon = val }
|
||||||
|
END { if (!skip && icon ~ /^[A-Za-z0-9._+-]+$/) print icon }
|
||||||
|
' "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Wanted: extra names, and the icons of the entries Steam sees (the first
|
||||||
|
# entry with a given desktop-file ID wins, like in the menu).
|
||||||
|
declare -A wanted=() seen=()
|
||||||
|
if [[ -n ${BREEZE_APPS:-} ]]; then
|
||||||
|
for name in "$@"; do wanted[$name]=extra; done
|
||||||
|
for d in "${data_dirs[@]}"; do
|
||||||
|
[[ -d $d/applications ]] || continue
|
||||||
|
while IFS= read -r -d '' f; do
|
||||||
|
id=${f#"$d/applications/"}; id=${id//\//-}
|
||||||
|
[[ -z ${seen[$id]:-} ]] || continue
|
||||||
|
seen[$id]=1
|
||||||
|
icon=$(entry_icon "$f")
|
||||||
|
[[ -z $icon ]] || wanted[$icon]=${wanted[$icon]:-$id}
|
||||||
|
done < <(find -L "$d/applications" -name '*.desktop' -print0 2>/dev/null)
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
changed=
|
||||||
|
linked=()
|
||||||
|
for name in "${!wanted[@]}"; do
|
||||||
|
resolves "$name" && continue
|
||||||
|
if ! src=$(breeze "$name"); then
|
||||||
|
[[ ${wanted[$name]} != extra ]] || echo "icon fallbacks: Breeze has no app icon \"$name\" (extra)" >&2
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
link=$dest/$name.svg
|
||||||
|
if [[ -e $link || -L $link ]] && ! ours "$link"; then continue; fi # not ours
|
||||||
|
linked+=("$name")
|
||||||
|
[[ $(readlink "$link") == "$src" ]] && continue
|
||||||
|
mkdir -p "$dest"
|
||||||
|
ln -sfn "$src" "$link"
|
||||||
|
echo "icon fallbacks: linked $name (${wanted[$name]})"
|
||||||
|
changed=1
|
||||||
|
done
|
||||||
|
|
||||||
|
# Remove links from earlier runs that are no longer wanted.
|
||||||
|
if [[ -f $manifest ]]; then
|
||||||
|
while IFS= read -r name; do
|
||||||
|
[[ -n $name && " ${linked[*]} " != *" $name "* ]] || continue
|
||||||
|
if ours "$dest/$name.svg"; then
|
||||||
|
rm -f "$dest/$name.svg"
|
||||||
|
echo "icon fallbacks: removed $name"
|
||||||
|
changed=1
|
||||||
|
fi
|
||||||
|
done < "$manifest"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if (( ${#linked[@]} )); then
|
||||||
|
mkdir -p "${manifest%/*}"
|
||||||
|
printf '%s\n' "${linked[@]}" | sort > "$manifest"
|
||||||
|
else
|
||||||
|
rm -f "$manifest"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# GTK rescans an icon theme only when a theme dir's mtime changes; bump it
|
||||||
|
# so a running Steam sees the change.
|
||||||
|
if [[ -n $changed && -d $data_home/icons/hicolor ]]; then touch "$data_home/icons/hicolor"; fi
|
||||||
@@ -26,6 +26,9 @@
|
|||||||
# closeOnLaunch = true;
|
# closeOnLaunch = true;
|
||||||
# launchDebounce = 10;
|
# launchDebounce = 10;
|
||||||
# grid = { enable = true; columns = 4; maxRows = 4; };
|
# grid = { enable = true; columns = 4; maxRows = 4; };
|
||||||
|
# # Icon fallbacks for programs without an icon in the menu are on by
|
||||||
|
# # default; names to provide in addition to what the scan finds:
|
||||||
|
# # iconFallbacks.extra = [ "system-file-manager" ];
|
||||||
# };
|
# };
|
||||||
# # SteamVR dashboard windows: resizable up to 4x (stock 2x), pushed
|
# # SteamVR dashboard windows: resizable up to 4x (stock 2x), pushed
|
||||||
# # back up to 10 m in the world / 12 m in theater mode (stock 5 / 6 m):
|
# # back up to 10 m in the world / 12 m in theater mode (stock 5 / 6 m):
|
||||||
|
|||||||
Reference in new issue
Block a user