refactor(bspwm): Cleanup states
This commit is contained in:
parent
466e9e212f
commit
6db66896bd
@ -33,7 +33,6 @@ class config {
|
||||
string build_path(const string& section, const string& key) const;
|
||||
void warn_deprecated(const string& section, const string& key, string replacement) const;
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if a given parameter exists
|
||||
*/
|
||||
|
@ -70,13 +70,9 @@ namespace drawtypes {
|
||||
};
|
||||
|
||||
label_t load_label(const config& conf, const string& section, string name, bool required = true, string def = "");
|
||||
|
||||
label_t load_optional_label(const config& conf, string section, string name, string def = "");
|
||||
|
||||
label_t load_either_config_label(const config& conf, string section, string name1, string name2, string def = "");
|
||||
|
||||
icon_t load_icon(const config& conf, string section, string name, bool required = true, string def = "");
|
||||
|
||||
icon_t load_optional_icon(const config& conf, string section, string name, string def = "");
|
||||
}
|
||||
|
||||
|
@ -6,42 +6,36 @@
|
||||
POLYBAR_NS
|
||||
|
||||
namespace modules {
|
||||
enum class state_ws {
|
||||
WORKSPACE_NONE,
|
||||
WORKSPACE_ACTIVE,
|
||||
WORKSPACE_URGENT,
|
||||
WORKSPACE_EMPTY,
|
||||
WORKSPACE_OCCUPIED,
|
||||
WORKSPACE_FOCUSED_URGENT,
|
||||
WORKSPACE_FOCUSED_EMPTY,
|
||||
WORKSPACE_FOCUSED_OCCUPIED,
|
||||
WORKSPACE_DIMMED, // used when the monitor is out of focus
|
||||
WORKSPACE_DIMMED_ACTIVE,
|
||||
WORKSPACE_DIMMED_URGENT,
|
||||
WORKSPACE_DIMMED_EMPTY,
|
||||
WORKSPACE_DIMMED_OCCUPIED
|
||||
class bspwm_module : public event_module<bspwm_module> {
|
||||
public:
|
||||
enum class state {
|
||||
NONE = 0U,
|
||||
EMPTY,
|
||||
OCCUPIED,
|
||||
FOCUSED,
|
||||
URGENT,
|
||||
DIMMED, // used when the monitor is out of focus
|
||||
};
|
||||
enum class state_mode {
|
||||
MODE_NONE,
|
||||
MODE_LAYOUT_MONOCLE,
|
||||
MODE_LAYOUT_TILED,
|
||||
MODE_STATE_FULLSCREEN,
|
||||
MODE_STATE_FLOATING,
|
||||
MODE_NODE_LOCKED,
|
||||
MODE_NODE_STICKY,
|
||||
MODE_NODE_PRIVATE
|
||||
|
||||
enum class mode {
|
||||
NONE = 0U,
|
||||
LAYOUT_MONOCLE,
|
||||
LAYOUT_TILED,
|
||||
STATE_FULLSCREEN,
|
||||
STATE_FLOATING,
|
||||
NODE_LOCKED,
|
||||
NODE_STICKY,
|
||||
NODE_PRIVATE
|
||||
};
|
||||
|
||||
struct bspwm_monitor {
|
||||
vector<pair<state_ws, label_t>> workspaces;
|
||||
vector<pair<uint32_t, label_t>> workspaces;
|
||||
vector<label_t> modes;
|
||||
label_t label;
|
||||
string name;
|
||||
bool focused = false;
|
||||
};
|
||||
|
||||
class bspwm_module : public event_module<bspwm_module> {
|
||||
public:
|
||||
using event_module::event_module;
|
||||
|
||||
void setup();
|
||||
@ -56,12 +50,14 @@ namespace modules {
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr auto DEFAULT_WS_ICON = "ws-icon-default";
|
||||
static constexpr auto DEFAULT_WS_LABEL = "%icon% %name%";
|
||||
static constexpr auto DEFAULT_ICON = "ws-icon-default";
|
||||
static constexpr auto DEFAULT_LABEL = "%icon% %name%";
|
||||
static constexpr auto DEFAULT_MONITOR_LABEL = "%name%";
|
||||
|
||||
static constexpr auto TAG_LABEL_MONITOR = "<label-monitor>";
|
||||
static constexpr auto TAG_LABEL_STATE = "<label-state>";
|
||||
static constexpr auto TAG_LABEL_MODE = "<label-mode>";
|
||||
|
||||
static constexpr auto EVENT_PREFIX = "bwm";
|
||||
static constexpr auto EVENT_CLICK = "bwmf";
|
||||
static constexpr auto EVENT_SCROLL_UP = "bwmn";
|
||||
@ -71,8 +67,8 @@ namespace modules {
|
||||
|
||||
vector<unique_ptr<bspwm_monitor>> m_monitors;
|
||||
|
||||
map<state_mode, label_t> m_modelabels;
|
||||
map<state_ws, label_t> m_statelabels;
|
||||
map<mode, label_t> m_modelabels;
|
||||
map<uint32_t, label_t> m_statelabels;
|
||||
label_t m_monitorlabel;
|
||||
iconset_t m_icons;
|
||||
|
||||
|
@ -167,13 +167,6 @@ namespace drawtypes {
|
||||
return load_label(conf, move(section), move(name), false, move(def));
|
||||
}
|
||||
|
||||
label_t load_either_config_label(const config& conf, string section, string name1, string name2, string def) {
|
||||
if (conf.has<string>(section, name1))
|
||||
return load_label(conf, section, name1, true, "");
|
||||
else
|
||||
return load_optional_label(conf, section, name2, def);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an icon by loading values from the configuration
|
||||
*/
|
||||
|
@ -10,6 +10,28 @@
|
||||
|
||||
POLYBAR_NS
|
||||
|
||||
namespace {
|
||||
using bspwm_state = modules::bspwm_module::state;
|
||||
|
||||
uint32_t make_mask(bspwm_state s1, bspwm_state s2 = bspwm_state::NONE) {
|
||||
uint32_t mask{0U};
|
||||
if (static_cast<uint32_t>(s1))
|
||||
mask |= 1 << (static_cast<uint32_t>(s1) - 1);
|
||||
if (static_cast<uint32_t>(s2))
|
||||
mask |= 1 << (static_cast<uint32_t>(s2) - 1);
|
||||
return mask;
|
||||
}
|
||||
|
||||
// uint32_t check_mask(uint32_t base, bspwm_state s1, bspwm_state s2 = bspwm_state::NONE) {
|
||||
// uint32_t mask{0U};
|
||||
// if (static_cast<uint32_t>(s1))
|
||||
// mask |= 1 << (static_cast<uint32_t>(s1) - 1);
|
||||
// if (static_cast<uint32_t>(s2))
|
||||
// mask |= 1 << (static_cast<uint32_t>(s2) - 1);
|
||||
// return (base & mask) == mask;
|
||||
// }
|
||||
}
|
||||
|
||||
namespace modules {
|
||||
template class module<bspwm_module>;
|
||||
template class event_module<bspwm_module>;
|
||||
@ -37,49 +59,65 @@ namespace modules {
|
||||
}
|
||||
|
||||
if (m_formatter->has(TAG_LABEL_STATE)) {
|
||||
m_statelabels.insert(
|
||||
make_pair(state_ws::WORKSPACE_ACTIVE, load_optional_label(m_conf, name(), "label-active", DEFAULT_WS_LABEL)));
|
||||
m_statelabels.insert(make_pair(
|
||||
state_ws::WORKSPACE_OCCUPIED, load_optional_label(m_conf, name(), "label-occupied", DEFAULT_WS_LABEL)));
|
||||
m_statelabels.insert(
|
||||
make_pair(state_ws::WORKSPACE_URGENT, load_optional_label(m_conf, name(), "label-urgent", DEFAULT_WS_LABEL)));
|
||||
m_statelabels.insert(
|
||||
make_pair(state_ws::WORKSPACE_OCCUPIED, load_optional_label(m_conf, name(), "label-occupied", DEFAULT_WS_LABEL)));
|
||||
m_statelabels.insert(
|
||||
make_pair(state_ws::WORKSPACE_EMPTY, load_optional_label(m_conf, name(), "label-empty", DEFAULT_WS_LABEL)));
|
||||
m_statelabels.insert(
|
||||
make_pair(state_ws::WORKSPACE_FOCUSED_OCCUPIED, load_either_config_label(m_conf, name(), "label-focused-occupied", "label-focused", DEFAULT_WS_LABEL)));
|
||||
m_statelabels.insert(
|
||||
make_pair(state_ws::WORKSPACE_FOCUSED_URGENT, load_either_config_label(m_conf, name(), "label-focused-urgent", "label-focused", DEFAULT_WS_LABEL)));
|
||||
m_statelabels.insert(
|
||||
make_pair(state_ws::WORKSPACE_FOCUSED_EMPTY, load_either_config_label(m_conf, name(), "label-focused-empty", "label-focused", DEFAULT_WS_LABEL)));
|
||||
m_statelabels.insert(make_pair(state_ws::WORKSPACE_DIMMED, load_optional_label(m_conf, name(), "label-dimmed")));
|
||||
m_statelabels.insert(
|
||||
make_pair(state_ws::WORKSPACE_DIMMED_ACTIVE, load_optional_label(m_conf, name(), "label-dimmed-active")));
|
||||
m_statelabels.insert(
|
||||
make_pair(state_ws::WORKSPACE_DIMMED_URGENT, load_optional_label(m_conf, name(), "label-dimmed-urgent")));
|
||||
m_statelabels.insert(
|
||||
make_pair(state_ws::WORKSPACE_DIMMED_EMPTY, load_optional_label(m_conf, name(), "label-dimmed-empty")));
|
||||
m_statelabels.insert(
|
||||
make_pair(state_ws::WORKSPACE_DIMMED_OCCUPIED, load_optional_label(m_conf, name(), "label-dimmed-occupied")));
|
||||
// XXX: Warn about deprecated parameters
|
||||
m_conf.warn_deprecated(name(), "label-dimmed-active", "label-dimmed-focused");
|
||||
|
||||
// clang-format off
|
||||
try {
|
||||
m_statelabels.emplace(make_mask(state::FOCUSED), load_label(m_conf, name(), "label-active", DEFAULT_LABEL));
|
||||
m_conf.warn_deprecated(name(), "label-active", "label-focused and label-dimmed-focused");
|
||||
} catch (const key_error& err) {
|
||||
m_statelabels.emplace(make_mask(state::FOCUSED), load_optional_label(m_conf, name(), "label-focused", DEFAULT_LABEL));
|
||||
}
|
||||
|
||||
m_statelabels.emplace(make_mask(state::OCCUPIED),
|
||||
load_optional_label(m_conf, name(), "label-occupied", DEFAULT_LABEL));
|
||||
m_statelabels.emplace(make_mask(state::URGENT),
|
||||
load_optional_label(m_conf, name(), "label-urgent", DEFAULT_LABEL));
|
||||
m_statelabels.emplace(make_mask(state::EMPTY),
|
||||
load_optional_label(m_conf, name(), "label-empty", DEFAULT_LABEL));
|
||||
m_statelabels.emplace(make_mask(state::DIMMED),
|
||||
load_optional_label(m_conf, name(), "label-dimmed"));
|
||||
|
||||
vector<pair<state, string>> focused_overrides{
|
||||
{state::OCCUPIED, "label-focused-occupied"},
|
||||
{state::URGENT, "label-focused-urgent"},
|
||||
{state::EMPTY, "label-focused-empty"}};
|
||||
|
||||
for (auto&& os : focused_overrides) {
|
||||
uint32_t mask{make_mask(state::FOCUSED, os.first)};
|
||||
try {
|
||||
m_statelabels.emplace(mask, load_label(m_conf, name(), os.second));
|
||||
} catch (const key_error& err) {
|
||||
m_statelabels.emplace(mask, m_statelabels.at(make_mask(state::FOCUSED))->clone());
|
||||
}
|
||||
}
|
||||
|
||||
vector<pair<state, string>> dimmed_overrides{
|
||||
{state::FOCUSED, "label-dimmed-focused"},
|
||||
{state::OCCUPIED, "label-dimmed-occupied"},
|
||||
{state::URGENT, "label-dimmed-urgent"},
|
||||
{state::EMPTY, "label-dimmed-empty"}};
|
||||
|
||||
for (auto&& os : dimmed_overrides) {
|
||||
m_statelabels.emplace(make_mask(state::DIMMED, os.first),
|
||||
load_optional_label(m_conf, name(), os.second, m_statelabels.at(make_mask(os.first))->get()));
|
||||
}
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
if (m_formatter->has(TAG_LABEL_MODE)) {
|
||||
m_modelabels.insert(
|
||||
make_pair(state_mode::MODE_LAYOUT_MONOCLE, load_optional_label(m_conf, name(), "label-monocle")));
|
||||
m_modelabels.insert(make_pair(state_mode::MODE_LAYOUT_TILED, load_optional_label(m_conf, name(), "label-tiled")));
|
||||
m_modelabels.insert(
|
||||
make_pair(state_mode::MODE_STATE_FULLSCREEN, load_optional_label(m_conf, name(), "label-fullscreen")));
|
||||
m_modelabels.insert(
|
||||
make_pair(state_mode::MODE_STATE_FLOATING, load_optional_label(m_conf, name(), "label-floating")));
|
||||
m_modelabels.insert(make_pair(state_mode::MODE_NODE_LOCKED, load_optional_label(m_conf, name(), "label-locked")));
|
||||
m_modelabels.insert(make_pair(state_mode::MODE_NODE_STICKY, load_optional_label(m_conf, name(), "label-sticky")));
|
||||
m_modelabels.insert(
|
||||
make_pair(state_mode::MODE_NODE_PRIVATE, load_optional_label(m_conf, name(), "label-private")));
|
||||
m_modelabels.emplace(mode::LAYOUT_MONOCLE, load_optional_label(m_conf, name(), "label-monocle"));
|
||||
m_modelabels.emplace(mode::LAYOUT_TILED, load_optional_label(m_conf, name(), "label-tiled"));
|
||||
m_modelabels.emplace(mode::STATE_FULLSCREEN, load_optional_label(m_conf, name(), "label-fullscreen"));
|
||||
m_modelabels.emplace(mode::STATE_FLOATING, load_optional_label(m_conf, name(), "label-floating"));
|
||||
m_modelabels.emplace(mode::NODE_LOCKED, load_optional_label(m_conf, name(), "label-locked"));
|
||||
m_modelabels.emplace(mode::NODE_STICKY, load_optional_label(m_conf, name(), "label-sticky"));
|
||||
m_modelabels.emplace(mode::NODE_PRIVATE, load_optional_label(m_conf, name(), "label-private"));
|
||||
}
|
||||
|
||||
m_icons = make_shared<iconset>();
|
||||
m_icons->add(DEFAULT_WS_ICON, make_shared<label>(m_conf.get<string>(name(), DEFAULT_WS_ICON, "")));
|
||||
m_icons->add(DEFAULT_ICON, make_shared<label>(m_conf.get<string>(name(), DEFAULT_ICON, "")));
|
||||
|
||||
for (const auto& workspace : m_conf.get_list<string>(name(), "ws-icon", {})) {
|
||||
auto vec = string_util::split(workspace, ';');
|
||||
@ -182,8 +220,8 @@ namespace modules {
|
||||
}
|
||||
|
||||
auto value = !tag.empty() ? tag.substr(1) : "";
|
||||
auto workspace_flag = state_ws::WORKSPACE_NONE;
|
||||
auto mode_flag = state_mode::MODE_NONE;
|
||||
auto mode_flag = mode::NONE;
|
||||
uint32_t workspace_mask{0U};
|
||||
|
||||
if (tag[0] == 'm' || tag[0] == 'M') {
|
||||
m_monitors.emplace_back(make_unique<bspwm_monitor>());
|
||||
@ -203,32 +241,32 @@ namespace modules {
|
||||
m_monitors.back()->focused = true;
|
||||
break;
|
||||
case 'F':
|
||||
workspace_flag = state_ws::WORKSPACE_FOCUSED_EMPTY;
|
||||
workspace_mask = make_mask(state::FOCUSED, state::EMPTY);
|
||||
break;
|
||||
case 'O':
|
||||
workspace_flag = state_ws::WORKSPACE_FOCUSED_OCCUPIED;
|
||||
workspace_mask = make_mask(state::FOCUSED, state::OCCUPIED);
|
||||
break;
|
||||
case 'U':
|
||||
workspace_flag = state_ws::WORKSPACE_FOCUSED_URGENT;
|
||||
workspace_mask = make_mask(state::FOCUSED, state::URGENT);
|
||||
break;
|
||||
case 'f':
|
||||
workspace_flag = state_ws::WORKSPACE_EMPTY;
|
||||
workspace_mask = make_mask(state::EMPTY);
|
||||
break;
|
||||
case 'o':
|
||||
workspace_flag = state_ws::WORKSPACE_OCCUPIED;
|
||||
workspace_mask = make_mask(state::OCCUPIED);
|
||||
break;
|
||||
case 'u':
|
||||
workspace_flag = state_ws::WORKSPACE_URGENT;
|
||||
workspace_mask = make_mask(state::URGENT);
|
||||
break;
|
||||
case 'L':
|
||||
switch (value[0]) {
|
||||
case 0:
|
||||
break;
|
||||
case 'M':
|
||||
mode_flag = state_mode::MODE_LAYOUT_MONOCLE;
|
||||
mode_flag = mode::LAYOUT_MONOCLE;
|
||||
break;
|
||||
case 'T':
|
||||
mode_flag = state_mode::MODE_LAYOUT_TILED;
|
||||
mode_flag = mode::LAYOUT_TILED;
|
||||
break;
|
||||
default:
|
||||
m_log.warn("%s: Undefined L => '%s'", name(), value);
|
||||
@ -242,10 +280,10 @@ namespace modules {
|
||||
case 'T':
|
||||
break;
|
||||
case '=':
|
||||
mode_flag = state_mode::MODE_STATE_FULLSCREEN;
|
||||
mode_flag = mode::STATE_FULLSCREEN;
|
||||
break;
|
||||
case 'F':
|
||||
mode_flag = state_mode::MODE_STATE_FLOATING;
|
||||
mode_flag = mode::STATE_FLOATING;
|
||||
break;
|
||||
default:
|
||||
m_log.warn("%s: Undefined T => '%s'", name(), value);
|
||||
@ -262,19 +300,19 @@ namespace modules {
|
||||
case 0:
|
||||
break;
|
||||
case 'L':
|
||||
mode_flag = state_mode::MODE_NODE_LOCKED;
|
||||
mode_flag = mode::NODE_LOCKED;
|
||||
break;
|
||||
case 'S':
|
||||
mode_flag = state_mode::MODE_NODE_STICKY;
|
||||
mode_flag = mode::NODE_STICKY;
|
||||
break;
|
||||
case 'P':
|
||||
mode_flag = state_mode::MODE_NODE_PRIVATE;
|
||||
mode_flag = mode::NODE_PRIVATE;
|
||||
break;
|
||||
default:
|
||||
m_log.warn("%s: Undefined G => '%s'", name(), value.substr(i, 1));
|
||||
}
|
||||
|
||||
if (mode_flag != state_mode::MODE_NONE && !m_modelabels.empty()) {
|
||||
if (mode_flag != mode::NONE && !m_modelabels.empty()) {
|
||||
m_monitors.back()->modes.emplace_back(m_modelabels.find(mode_flag)->second->clone());
|
||||
}
|
||||
}
|
||||
@ -284,28 +322,21 @@ namespace modules {
|
||||
m_log.warn("%s: Undefined tag => '%s'", name(), tag.substr(0, 1));
|
||||
}
|
||||
|
||||
if (workspace_flag != state_ws::WORKSPACE_NONE && m_formatter->has(TAG_LABEL_STATE)) {
|
||||
auto icon = m_icons->get(value, DEFAULT_WS_ICON);
|
||||
auto label = m_statelabels.find(workspace_flag)->second->clone();
|
||||
if (workspace_mask && m_formatter->has(TAG_LABEL_STATE)) {
|
||||
auto icon = m_icons->get(value, DEFAULT_ICON);
|
||||
auto label = m_statelabels.at(workspace_mask)->clone();
|
||||
|
||||
if (!m_monitors.back()->focused) {
|
||||
label->replace_defined_values(m_statelabels.find(state_ws::WORKSPACE_DIMMED)->second);
|
||||
switch (workspace_flag) {
|
||||
case state_ws::WORKSPACE_ACTIVE:
|
||||
label->replace_defined_values(m_statelabels.find(state_ws::WORKSPACE_DIMMED_ACTIVE)->second);
|
||||
break;
|
||||
case state_ws::WORKSPACE_OCCUPIED:
|
||||
label->replace_defined_values(m_statelabels.find(state_ws::WORKSPACE_DIMMED_OCCUPIED)->second);
|
||||
break;
|
||||
case state_ws::WORKSPACE_URGENT:
|
||||
label->replace_defined_values(m_statelabels.find(state_ws::WORKSPACE_DIMMED_URGENT)->second);
|
||||
break;
|
||||
case state_ws::WORKSPACE_EMPTY:
|
||||
label->replace_defined_values(m_statelabels.find(state_ws::WORKSPACE_DIMMED_EMPTY)->second);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (m_statelabels[make_mask(state::DIMMED)])
|
||||
label->replace_defined_values(m_statelabels[make_mask(state::DIMMED)]);
|
||||
if (workspace_mask & make_mask(state::EMPTY))
|
||||
label->replace_defined_values(m_statelabels[make_mask(state::DIMMED, state::EMPTY)]);
|
||||
if (workspace_mask & make_mask(state::OCCUPIED))
|
||||
label->replace_defined_values(m_statelabels[make_mask(state::DIMMED, state::OCCUPIED)]);
|
||||
if (workspace_mask & make_mask(state::FOCUSED))
|
||||
label->replace_defined_values(m_statelabels[make_mask(state::DIMMED, state::FOCUSED)]);
|
||||
if (workspace_mask & make_mask(state::URGENT))
|
||||
label->replace_defined_values(m_statelabels[make_mask(state::DIMMED, state::URGENT)]);
|
||||
}
|
||||
|
||||
label->reset_tokens();
|
||||
@ -313,10 +344,10 @@ namespace modules {
|
||||
label->replace_token("%icon%", icon->get());
|
||||
label->replace_token("%index%", to_string(++workspace_n));
|
||||
|
||||
m_monitors.back()->workspaces.emplace_back(make_pair(workspace_flag, move(label)));
|
||||
m_monitors.back()->workspaces.emplace_back(workspace_mask, move(label));
|
||||
}
|
||||
|
||||
if (mode_flag != state_mode::MODE_NONE && !m_modelabels.empty()) {
|
||||
if (mode_flag != mode::NONE && !m_modelabels.empty()) {
|
||||
m_monitors.back()->modes.emplace_back(m_modelabels.find(mode_flag)->second->clone());
|
||||
}
|
||||
}
|
||||
@ -339,7 +370,7 @@ namespace modules {
|
||||
if (tag == TAG_LABEL_MONITOR) {
|
||||
builder->node(m_monitors[m_index]->label);
|
||||
return true;
|
||||
} else if (tag == TAG_LABEL_STATE) {
|
||||
} else if (tag == TAG_LABEL_STATE && !m_monitors[m_index]->workspaces.empty()) {
|
||||
int workspace_n = 0;
|
||||
|
||||
if (m_scroll) {
|
||||
@ -350,12 +381,7 @@ namespace modules {
|
||||
for (auto&& ws : m_monitors[m_index]->workspaces) {
|
||||
if (ws.second.get()) {
|
||||
if (m_click) {
|
||||
string event{EVENT_CLICK};
|
||||
event += to_string(m_index);
|
||||
event += "+";
|
||||
event += to_string(++workspace_n);
|
||||
|
||||
builder->cmd(mousebtn::LEFT, event);
|
||||
builder->cmd(mousebtn::LEFT, EVENT_CLICK + to_string(m_index) + "+" + to_string(++workspace_n));
|
||||
builder->node(ws.second);
|
||||
builder->cmd_close();
|
||||
} else {
|
||||
@ -371,7 +397,7 @@ namespace modules {
|
||||
}
|
||||
|
||||
return workspace_n > 0;
|
||||
} else if (tag == TAG_LABEL_MODE && m_monitors[m_index]->focused) {
|
||||
} else if (tag == TAG_LABEL_MODE && m_monitors[m_index]->focused && !m_monitors[m_index]->modes.empty()) {
|
||||
int modes_n = 0;
|
||||
|
||||
for (auto&& mode : m_monitors[m_index]->modes) {
|
||||
|
Loading…
Reference in New Issue
Block a user