Compare commits
2 Commits
0a94acb8f4
...
f1c68321a7
| Author | SHA1 | Date | |
|---|---|---|---|
| f1c68321a7 | |||
| e1eeb370da |
5
.clangd
Normal file
5
.clangd
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
CompileFlags:
|
||||||
|
Remove:
|
||||||
|
- -fmodules-ts
|
||||||
|
- -fmodule-mapper=*
|
||||||
|
- -fdeps-format=*
|
||||||
@@ -34,6 +34,7 @@ target_sources(bar_lib
|
|||||||
src/app.cpp
|
src/app.cpp
|
||||||
src/bar/bar.cpp
|
src/bar/bar.cpp
|
||||||
src/widgets/clock.cpp
|
src/widgets/clock.cpp
|
||||||
|
src/widgets/date.cpp
|
||||||
src/widgets/workspaceIndicator.cpp
|
src/widgets/workspaceIndicator.cpp
|
||||||
src/widgets/volumeWidget.cpp
|
src/widgets/volumeWidget.cpp
|
||||||
src/widgets/webWidget.cpp
|
src/widgets/webWidget.cpp
|
||||||
@@ -52,6 +53,7 @@ target_link_libraries(bar bar_lib ${GTKMM_LIBRARIES} ${LAYERSHELL_LIBRARIES} ${W
|
|||||||
# Copy `resources/bar.css` into the build directory when it changes
|
# Copy `resources/bar.css` into the build directory when it changes
|
||||||
set(RES_SRC "${CMAKE_CURRENT_SOURCE_DIR}/resources/bar.css")
|
set(RES_SRC "${CMAKE_CURRENT_SOURCE_DIR}/resources/bar.css")
|
||||||
set(RES_DST "${CMAKE_CURRENT_BINARY_DIR}/resources/bar.css")
|
set(RES_DST "${CMAKE_CURRENT_BINARY_DIR}/resources/bar.css")
|
||||||
|
set(USER_CONFIG_CSS "$ENV{HOME}/.config/bar/bar.css")
|
||||||
|
|
||||||
add_custom_command(
|
add_custom_command(
|
||||||
OUTPUT ${RES_DST}
|
OUTPUT ${RES_DST}
|
||||||
@@ -62,5 +64,14 @@ add_custom_command(
|
|||||||
VERBATIM
|
VERBATIM
|
||||||
)
|
)
|
||||||
|
|
||||||
add_custom_target(copy_resources ALL DEPENDS ${RES_DST})
|
add_custom_command(
|
||||||
|
OUTPUT ${USER_CONFIG_CSS}
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E make_directory "$ENV{HOME}/.config/bar"
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${RES_SRC}" "${USER_CONFIG_CSS}"
|
||||||
|
DEPENDS "${RES_SRC}"
|
||||||
|
COMMENT "Copy resources/bar.css to ~/.config/bar/bar.css"
|
||||||
|
VERBATIM
|
||||||
|
)
|
||||||
|
|
||||||
|
add_custom_target(copy_resources ALL DEPENDS ${RES_DST} ${USER_CONFIG_CSS})
|
||||||
add_dependencies(bar copy_resources)
|
add_dependencies(bar copy_resources)
|
||||||
|
|||||||
@@ -6,9 +6,11 @@
|
|||||||
#include "services/hyprland.hpp"
|
#include "services/hyprland.hpp"
|
||||||
#include "services/tray.hpp"
|
#include "services/tray.hpp"
|
||||||
#include "widgets/clock.hpp"
|
#include "widgets/clock.hpp"
|
||||||
|
#include "widgets/date.hpp"
|
||||||
#include "widgets/tray.hpp"
|
#include "widgets/tray.hpp"
|
||||||
#include "widgets/webWidget.hpp"
|
#include "widgets/webWidget.hpp"
|
||||||
#include "widgets/workspaceIndicator.hpp"
|
#include "widgets/workspaceIndicator.hpp"
|
||||||
|
#include "icons.hpp"
|
||||||
|
|
||||||
class Bar : public Gtk::Window {
|
class Bar : public Gtk::Window {
|
||||||
public:
|
public:
|
||||||
@@ -23,7 +25,8 @@ class Bar : public Gtk::Window {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
Clock clock;
|
Clock clock;
|
||||||
WebWidget homeAssistant {"HA", "Home Assistant",
|
Date date;
|
||||||
|
WebWidget homeAssistant {ICON_HOME, "Home Assistant",
|
||||||
"https://home.rivercry.com"};
|
"https://home.rivercry.com"};
|
||||||
TrayService &trayService;
|
TrayService &trayService;
|
||||||
HyprlandService &hyprlandService;
|
HyprlandService &hyprlandService;
|
||||||
|
|||||||
3
include/icons.hpp
Normal file
3
include/icons.hpp
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define ICON_HOME "\ue88a"
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
class IUpdatable {
|
class IUpdatable {
|
||||||
public:
|
public:
|
||||||
virtual ~IUpdatable() = default;
|
virtual ~IUpdatable() = default;
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ class HyprlandService {
|
|||||||
std::string get_socket_path();
|
std::string get_socket_path();
|
||||||
void refresh_monitors();
|
void refresh_monitors();
|
||||||
void refresh_workspaces();
|
void refresh_workspaces();
|
||||||
|
void handle_urgent_window(std::string windowAddress);
|
||||||
};
|
};
|
||||||
|
|
||||||
inline void HyprlandService::printMonitor(const Monitor &mon) const {
|
inline void HyprlandService::printMonitor(const Monitor &mon) const {
|
||||||
|
|||||||
12
include/widgets/date.hpp
Normal file
12
include/widgets/date.hpp
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <gtk4-layer-shell/gtk4-layer-shell.h>
|
||||||
|
#include <gtkmm.h>
|
||||||
|
#include <gtkmm/label.h>
|
||||||
|
|
||||||
|
#include "interface/updateable.ipp"
|
||||||
|
|
||||||
|
class Date : public Gtk::Label, public IUpdatable {
|
||||||
|
public:
|
||||||
|
bool onUpdate();
|
||||||
|
};
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
class WebWidget : public Gtk::Button {
|
class WebWidget : public Gtk::Button {
|
||||||
public:
|
public:
|
||||||
WebWidget(std::string label, std::string title, std::string url);
|
WebWidget(std::string icon, std::string title, std::string url);
|
||||||
~WebWidget() override;
|
~WebWidget() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
all: unset;
|
all: unset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
window {
|
window {
|
||||||
background-color: rgba(30, 30, 30, 0.8);
|
background-color: rgba(30, 30, 30, 0.8);
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
@@ -21,24 +22,28 @@ window {
|
|||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.workspace-pill:hover {
|
|
||||||
background-color: rgba(104, 104, 104, 0.2);
|
|
||||||
}
|
|
||||||
.workspace-pill:last-child {
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.workspace-pill-focused {
|
.workspace-pill-focused {
|
||||||
background-color: rgba(104, 104, 104, 0.3);
|
background-color: #ffffff;
|
||||||
|
color: #1e1e1e;
|
||||||
|
font-weight: bold;
|
||||||
|
border-bottom: #89b4fa 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.workspace-pill-active {
|
.workspace-pill-active {
|
||||||
background-color: rgba(168, 168, 168, 0.4);
|
background-color: rgba(255, 255, 255, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.workspace-pill-urgent {
|
.workspace-pill-urgent {
|
||||||
background-color: #ff5555;
|
background-color: #ff5555;
|
||||||
color: #111;
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workspace-pill:last-child {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workspace-pill:hover {
|
||||||
|
background-color: rgba(255, 255, 255, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.minimized {
|
.minimized {
|
||||||
@@ -49,7 +54,6 @@ window {
|
|||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
button {
|
button {
|
||||||
padding: 2px 5px;
|
padding: 2px 5px;
|
||||||
margin: 0 2px;
|
margin: 0 2px;
|
||||||
@@ -59,13 +63,10 @@ button {
|
|||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Hover effect: slightly brighten background and show pointer */
|
|
||||||
button:hover {
|
button:hover {
|
||||||
background-color: #111111;
|
background-color: #111111;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#spacer {
|
#spacer {
|
||||||
color: rgba(255, 255, 255, 0.3);
|
color: rgba(255, 255, 255, 0.3);
|
||||||
padding: 0 5px;
|
padding: 0 5px;
|
||||||
@@ -82,4 +83,9 @@ tooltip {
|
|||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
font-family: "IBMPlexSans-Regular", sans-serif;
|
font-family: "IBMPlexSans-Regular", sans-serif;
|
||||||
padding: 5px 10px;
|
padding: 5px 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.icon-label {
|
||||||
|
font-family: "Material Icons, Hack Nerd Font Mono";
|
||||||
|
font-size: 19px;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
#include "bar/bar.hpp"
|
#include "bar/bar.hpp"
|
||||||
#include "gtk/gtk.h"
|
#include "gtk/gtk.h"
|
||||||
|
#include "widgets/date.hpp"
|
||||||
#include "widgets/spacer.hpp"
|
#include "widgets/spacer.hpp"
|
||||||
#include "widgets/volumeWidget.hpp"
|
#include "widgets/volumeWidget.hpp"
|
||||||
#include "widgets/workspaceIndicator.hpp"
|
#include "widgets/workspaceIndicator.hpp"
|
||||||
|
|
||||||
#include "helpers/systemHelper.hpp"
|
#include "helpers/systemHelper.hpp"
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
#include <gtkmm/enums.h>
|
#include <gtkmm/enums.h>
|
||||||
#include <gtkmm/label.h>
|
#include <gtkmm/label.h>
|
||||||
#include <gtkmm/window.h>
|
#include <gtkmm/window.h>
|
||||||
@@ -33,6 +35,9 @@ Bar::Bar(GdkMonitor *monitor, HyprlandService &hyprlandService,
|
|||||||
|
|
||||||
set_child(main_box);
|
set_child(main_box);
|
||||||
|
|
||||||
|
|
||||||
|
this->volumeWidget = Gtk::make_managed<VolumeWidget>();
|
||||||
|
|
||||||
load_css();
|
load_css();
|
||||||
setup_ui();
|
setup_ui();
|
||||||
|
|
||||||
@@ -41,6 +46,13 @@ Bar::Bar(GdkMonitor *monitor, HyprlandService &hyprlandService,
|
|||||||
Glib::signal_timeout().connect(sigc::mem_fun(clock, &Clock::onUpdate),
|
Glib::signal_timeout().connect(sigc::mem_fun(clock, &Clock::onUpdate),
|
||||||
1000);
|
1000);
|
||||||
|
|
||||||
|
|
||||||
|
date.onUpdate();
|
||||||
|
|
||||||
|
Glib::signal_timeout().connect(sigc::mem_fun(date, &Date::onUpdate),
|
||||||
|
1000);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Bar::setup_ui() {
|
void Bar::setup_ui() {
|
||||||
@@ -65,14 +77,11 @@ void Bar::setup_ui() {
|
|||||||
left_box.append(*workspaceIndicator);
|
left_box.append(*workspaceIndicator);
|
||||||
|
|
||||||
clock.set_name("clock-label");
|
clock.set_name("clock-label");
|
||||||
clock.set_hexpand(false);
|
center_box.append(this->date);
|
||||||
clock.set_halign(Gtk::Align::CENTER);
|
|
||||||
clock.set_valign(Gtk::Align::CENTER);
|
|
||||||
center_box.append(clock);
|
|
||||||
center_box.append(*(new Spacer()));
|
center_box.append(*(new Spacer()));
|
||||||
|
center_box.append(this->clock);
|
||||||
volumeWidget = Gtk::make_managed<VolumeWidget>();
|
center_box.append(*(new Spacer()));
|
||||||
center_box.append(*volumeWidget);
|
center_box.append(*this->volumeWidget);
|
||||||
|
|
||||||
|
|
||||||
trayWidget = Gtk::make_managed<TrayWidget>(trayService);
|
trayWidget = Gtk::make_managed<TrayWidget>(trayService);
|
||||||
@@ -83,8 +92,17 @@ void Bar::setup_ui() {
|
|||||||
void Bar::load_css() {
|
void Bar::load_css() {
|
||||||
auto css_provider = Gtk::CssProvider::create();
|
auto css_provider = Gtk::CssProvider::create();
|
||||||
|
|
||||||
|
std::string css_path = "resources/bar.css";
|
||||||
|
const char* home = std::getenv("HOME");
|
||||||
|
if (home) {
|
||||||
|
std::filesystem::path config_path = std::filesystem::path(home) / ".config/bar/bar.css";
|
||||||
|
if (std::filesystem::exists(config_path)) {
|
||||||
|
css_path = config_path.string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const std::string css =
|
const std::string css =
|
||||||
SystemHelper::read_file_to_string("resources/bar.css");
|
SystemHelper::read_file_to_string(css_path);
|
||||||
css_provider->load_from_data(css);
|
css_provider->load_from_data(css);
|
||||||
|
|
||||||
Gtk::StyleContext::add_provider_for_display(
|
Gtk::StyleContext::add_provider_for_display(
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
namespace {
|
namespace {
|
||||||
const char *kMonitorCommand = "hyprctl monitors -j";
|
const char *kMonitorCommand = "hyprctl monitors -j";
|
||||||
const char *kWorkspaceCommand = "hyprctl workspaces -j";
|
const char *kWorkspaceCommand = "hyprctl workspaces -j";
|
||||||
|
const char *kClientsCommand = "hyprctl clients -j";
|
||||||
|
|
||||||
bool is_workspace_event(const std::string &event) {
|
bool is_workspace_event(const std::string &event) {
|
||||||
return event.find("workspace") != std::string::npos;
|
return event.find("workspace") != std::string::npos;
|
||||||
@@ -32,8 +33,11 @@ HyprlandService::~HyprlandService() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void HyprlandService::on_hyprland_event(std::string event,
|
void HyprlandService::on_hyprland_event(std::string event, std::string data) {
|
||||||
std::string /*data*/) {
|
if (event == "urgent") {
|
||||||
|
handle_urgent_window(data);
|
||||||
|
}
|
||||||
|
|
||||||
if (is_workspace_event(event) || event == "focusedmon" ||
|
if (is_workspace_event(event) || event == "focusedmon" ||
|
||||||
event == "monitoradded" || event == "monitorremoved") {
|
event == "monitoradded" || event == "monitorremoved") {
|
||||||
refresh_monitors();
|
refresh_monitors();
|
||||||
@@ -213,8 +217,8 @@ void HyprlandService::refresh_workspaces() {
|
|||||||
state.id = slot;
|
state.id = slot;
|
||||||
state.hyprId = -1;
|
state.hyprId = -1;
|
||||||
state.label = std::to_string(slot);
|
state.label = std::to_string(slot);
|
||||||
state.focused = (slot == focusedSlot);
|
state.active = (slot == focusedSlot);
|
||||||
state.active = state.focused;
|
state.focused = state.focused;
|
||||||
state.urgent = false;
|
state.urgent = false;
|
||||||
monitor.workspaceStates.emplace(slot, state);
|
monitor.workspaceStates.emplace(slot, state);
|
||||||
}
|
}
|
||||||
@@ -326,4 +330,58 @@ HyprlandService::getMonitorByIndex(std::size_t index) const {
|
|||||||
auto it = monitors.begin();
|
auto it = monitors.begin();
|
||||||
std::advance(it, static_cast<long>(index));
|
std::advance(it, static_cast<long>(index));
|
||||||
return &it->second;
|
return &it->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void HyprlandService::handle_urgent_window(std::string windowAddress) {
|
||||||
|
std::string output;
|
||||||
|
try {
|
||||||
|
output = SystemHelper::get_command_output(kClientsCommand);
|
||||||
|
} catch (const std::exception &ex) {
|
||||||
|
std::cerr << "[Hyprland] Failed to query clients: " << ex.what()
|
||||||
|
<< std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto clientsJson = nlohmann::json::parse(output, nullptr, false);
|
||||||
|
if (!clientsJson.is_array()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int workspaceId = -1;
|
||||||
|
|
||||||
|
for (const auto &client : clientsJson) {
|
||||||
|
if (!client.is_object())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
std::string addr = client.value("address", "");
|
||||||
|
if (addr == "0x" + windowAddress) {
|
||||||
|
if (client.contains("workspace") &&
|
||||||
|
client["workspace"].is_object()) {
|
||||||
|
workspaceId = client["workspace"].value("id", -1);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (workspaceId == -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto &pair : monitors) {
|
||||||
|
auto &monitor = pair.second;
|
||||||
|
bool changed = false;
|
||||||
|
|
||||||
|
for (auto &wsPair : monitor.workspaceStates) {
|
||||||
|
if (wsPair.second.hyprId == workspaceId) {
|
||||||
|
if (!wsPair.second.urgent) {
|
||||||
|
wsPair.second.urgent = true;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changed) {
|
||||||
|
workspaceStateChanged.emit(monitor.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
16
src/widgets/date.cpp
Normal file
16
src/widgets/date.cpp
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
#include "widgets/date.hpp"
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <iomanip>
|
||||||
|
|
||||||
|
bool Date::onUpdate() {
|
||||||
|
auto now =
|
||||||
|
std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
|
||||||
|
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << std::put_time(std::localtime(&now), "%a, %d.%m.%Y");
|
||||||
|
|
||||||
|
set_text(ss.str());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
@@ -1,9 +1,13 @@
|
|||||||
#include "widgets/webWidget.hpp"
|
#include "widgets/webWidget.hpp"
|
||||||
#include <gtkmm/box.h>
|
#include <gtkmm/box.h>
|
||||||
|
#include <gtkmm/label.h>
|
||||||
#include <webkit/webkit.h>
|
#include <webkit/webkit.h>
|
||||||
|
|
||||||
WebWidget::WebWidget(std::string label, std::string title, std::string url) {
|
WebWidget::WebWidget(std::string icon, std::string title, std::string url) {
|
||||||
set_label(label);
|
auto label = Gtk::make_managed<Gtk::Label>(icon);
|
||||||
|
label->add_css_class("icon-label");
|
||||||
|
set_child(*label);
|
||||||
|
|
||||||
signal_clicked().connect(
|
signal_clicked().connect(
|
||||||
sigc::mem_fun(*this, &WebWidget::on_toggle_window));
|
sigc::mem_fun(*this, &WebWidget::on_toggle_window));
|
||||||
|
|
||||||
@@ -23,12 +27,10 @@ WebWidget::WebWidget(std::string label, std::string title, std::string url) {
|
|||||||
|
|
||||||
auto settings = webkit_web_view_get_settings(WEBKIT_WEB_VIEW(webview));
|
auto settings = webkit_web_view_get_settings(WEBKIT_WEB_VIEW(webview));
|
||||||
webkit_settings_set_hardware_acceleration_policy(
|
webkit_settings_set_hardware_acceleration_policy(
|
||||||
settings, WEBKIT_HARDWARE_ACCELERATION_POLICY_ALWAYS);
|
settings, WEBKIT_HARDWARE_ACCELERATION_POLICY_NEVER);
|
||||||
|
|
||||||
webkit_web_view_load_uri(WEBKIT_WEB_VIEW(webview), url.c_str());
|
webkit_web_view_load_uri(WEBKIT_WEB_VIEW(webview), url.c_str());
|
||||||
|
|
||||||
// Use C API to set child because we don't have a C++ wrapper for
|
|
||||||
// WebKitWebView
|
|
||||||
gtk_popover_set_child(popover->gobj(), webview);
|
gtk_popover_set_child(popover->gobj(), webview);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ WorkspaceIndicator::~WorkspaceIndicator() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void WorkspaceIndicator::on_workspace_update(int monitorId) {
|
void WorkspaceIndicator::on_workspace_update(int monitorId) {
|
||||||
if (monitorId != monitorId && monitorId != -1) {
|
if (this->monitorId != monitorId && monitorId != -1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,13 +81,13 @@ void WorkspaceIndicator::rebuild() {
|
|||||||
label->add_controller(gesture);
|
label->add_controller(gesture);
|
||||||
|
|
||||||
if (state != nullptr) {
|
if (state != nullptr) {
|
||||||
if (state->focused) {
|
if (state->urgent != true) {
|
||||||
label->add_css_class("workspace-pill-focused");
|
if (state->focused) {
|
||||||
} else if (state->active) {
|
label->add_css_class("workspace-pill-focused");
|
||||||
label->add_css_class("workspace-pill-active");
|
} else if (state->active) {
|
||||||
}
|
label->add_css_class("workspace-pill-active");
|
||||||
|
}
|
||||||
if (state->urgent) {
|
} else {
|
||||||
label->add_css_class("workspace-pill-urgent");
|
label->add_css_class("workspace-pill-urgent");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user