diff --git a/include/bar.hpp b/include/bar.hpp index 783dd282..f3dfa781 100644 --- a/include/bar.hpp +++ b/include/bar.hpp @@ -24,6 +24,8 @@ struct waybar_output { Glib::RefPtr monitor; std::string name; std::string identifier; + int32_t width; + int32_t height; std::unique_ptr xdg_output = { nullptr, &zxdg_output_v1_destroy}; diff --git a/include/client.hpp b/include/client.hpp index 09f9090f..7971c132 100644 --- a/include/client.hpp +++ b/include/client.hpp @@ -45,6 +45,7 @@ class Client { static void handleGlobal(void* data, struct wl_registry* registry, uint32_t name, const char* interface, uint32_t version); static void handleGlobalRemove(void* data, struct wl_registry* registry, uint32_t name); + static void handleOutputLogicalSize(void*, struct zxdg_output_v1*, int32_t, int32_t); static void handleOutputDone(void*, struct zxdg_output_v1*); void createBarsBatch(); static void handleOutputName(void*, struct zxdg_output_v1*, const char*); diff --git a/include/config.hpp b/include/config.hpp index 1ef9ef5f..3b3b7243 100644 --- a/include/config.hpp +++ b/include/config.hpp @@ -29,7 +29,8 @@ class Config { Json::Value& getConfig() { return config_; } - std::vector getOutputConfigs(const std::string& name, const std::string& identifier); + std::vector getOutputConfigs(const std::string& name, const std::string& identifier, + int32_t width, int32_t height); private: void setupConfig(Json::Value& dst, const std::string& config_file, int depth); diff --git a/man/waybar.5.scd.in b/man/waybar.5.scd.in index 4209c053..e52dadee 100644 --- a/man/waybar.5.scd.in +++ b/man/waybar.5.scd.in @@ -50,6 +50,10 @@ The visual display elements for waybar use a CSS stylesheet. Waybar supports *st Output specification follows sway's and can either be the output port such as "HDMI-A-1" or a string consisting of the make, model, and serial such as "Some Company ABC123 0x00000000". See *sway-output(5)* for details. In an array, star '*\**' can be used at the end to accept all outputs, in case all previous entries are exclusions. +*output-dimensions* ++ + typeof: string|array ++ + If *output* is not specified, allows selecting outputs by dimensions. Format "(width/height) () (value)", e.g. "width > 1080". Multiple conditions can be chained. + *position* ++ typeof: string ++ default: top ++ @@ -417,6 +421,24 @@ To hide the power group if it is empty, apply to #power and all its children: } ``` +## Select outputs by dimensions + +``` +{ + "layer": "top", + "output-dimensions": "width > 1080", + ... +} +``` + +``` +{ + "layer": "top", + "output-dimensions": ["width < 3840", "height < 2160"], + ... +} +``` + # SUPPORTED MODULES - *waybar-backlight(5)* diff --git a/src/client.cpp b/src/client.cpp index 71aa06ef..57e0fe5c 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -58,7 +58,7 @@ void waybar::Client::handleGlobalRemove(void* data, struct wl_registry* /*regist void waybar::Client::handleOutput(struct waybar_output& output) { static const struct zxdg_output_v1_listener xdgOutputListener = { .logical_position = [](void*, struct zxdg_output_v1*, int32_t, int32_t) {}, - .logical_size = [](void*, struct zxdg_output_v1*, int32_t, int32_t) {}, + .logical_size = &handleOutputLogicalSize, .done = &handleOutputDone, .name = &handleOutputName, .description = &handleOutputDescription, @@ -79,7 +79,19 @@ struct waybar::waybar_output& waybar::Client::getOutput(void* addr) { } std::vector waybar::Client::getOutputConfigs(struct waybar_output& output) { - return config.getOutputConfigs(output.name, output.identifier); + return config.getOutputConfigs(output.name, output.identifier, output.width, output.height); +} + +void waybar::Client::handleOutputLogicalSize(void* data, struct zxdg_output_v1* object, + int32_t width, int32_t height) { + auto client = waybar::Client::inst(); + try { + auto& output = client->getOutput(data); + output.width = width; + output.height = height; + } catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + } } void waybar::Client::handleOutputDone(void* data, struct zxdg_output_v1* /*xdg_output*/) { diff --git a/src/config.cpp b/src/config.cpp index 7b3feaea..51bec392 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -174,8 +174,8 @@ void Config::mergeConfig(Json::Value& a_config_, Json::Value& b_config_) { spdlog::error("Cannot merge config, conflicting or invalid JSON types"); } } -bool isValidOutput(const Json::Value& config, const std::string& name, - const std::string& identifier) { +bool isValidOutput(const Json::Value& config, const std::string& name, const std::string& identifier, + int32_t width, int32_t height) { const auto isOutputMatches = [&](const std::string& output) -> bool { if (output.substr(0, 1) == "$") { auto* const environment_value = std::getenv(output.substr(1).c_str()); @@ -227,6 +227,38 @@ bool isValidOutput(const Json::Value& config, const std::string& name, } } + // if "output-dimensions" is a string, make it an array of size 1 + Json::Value config_output_dimensions = config["output-dimensions"]; + if (config_output_dimensions.isString()) { + Json::Value jsonArray(Json::arrayValue); + jsonArray.append(config_output_dimensions); + config_output_dimensions = jsonArray; + } + if (config_output_dimensions.isArray()) { + for (auto const &config_output_dimension : config_output_dimensions) { + if (!config_output_dimension.isString()) { + continue; + } + std::string str = config_output_dimension.asString(); + int i = str.find(" "); + std::string dimension = str.substr(0, i); + str = str.substr(i + 1); + i = str.find(" "); + std::string comparator = str.substr(0, i); + int value = std::stoi(str.substr(i)); + + if (dimension == "height" && comparator == "<" && height >= value) { + return false; + } else if (dimension == "height" && comparator == ">" && height <= value) { + return false; + }else if (dimension == "width" && comparator == "<" && width >= value) { + return false; + }else if (dimension == "width" && comparator == ">" && width <= value) { + return false; + } + return true; + } + } return true; } @@ -242,15 +274,16 @@ void Config::load(const std::string& config) { } std::vector Config::getOutputConfigs(const std::string& name, - const std::string& identifier) { + const std::string& identifier, int32_t width, + int32_t height) { std::vector configs; if (config_.isArray()) { for (auto const& config : config_) { - if (config.isObject() && isValidOutput(config, name, identifier)) { + if (config.isObject() && isValidOutput(config, name, identifier, width, height)) { configs.push_back(config); } } - } else if (isValidOutput(config_, name, identifier)) { + } else if (isValidOutput(config_, name, identifier, width, height)) { configs.push_back(config_); } return configs; diff --git a/test/config.cpp b/test/config.cpp index 28e7c2de..27e84b9b 100644 --- a/test/config.cpp +++ b/test/config.cpp @@ -16,11 +16,11 @@ TEST_CASE("Load simple config", "[config]") { REQUIRE(data["height"].asInt() == 30); } SECTION("select configs for configured output") { - auto configs = conf.getOutputConfigs("HDMI-0", "Fake HDMI output #0"); + auto configs = conf.getOutputConfigs("HDMI-0", "Fake HDMI output #0", 0, 0); REQUIRE(configs.size() == 1); } SECTION("select configs for missing output") { - auto configs = conf.getOutputConfigs("HDMI-1", "Fake HDMI output #1"); + auto configs = conf.getOutputConfigs("HDMI-1", "Fake HDMI output #1", 0, 0); REQUIRE(configs.empty()); } } @@ -30,7 +30,7 @@ TEST_CASE("Load config with multiple bars", "[config]") { conf.load("test/config/multi.json"); SECTION("select multiple configs #1") { - auto data = conf.getOutputConfigs("DP-0", "Fake DisplayPort output #0"); + auto data = conf.getOutputConfigs("DP-0", "Fake DisplayPort output #0", 0, 0); REQUIRE(data.size() == 4); REQUIRE(data[0]["layer"].asString() == "bottom"); REQUIRE(data[0]["height"].asInt() == 20); @@ -43,7 +43,7 @@ TEST_CASE("Load config with multiple bars", "[config]") { REQUIRE(data[3]["height"].asInt() == 24); } SECTION("select multiple configs #2") { - auto data = conf.getOutputConfigs("HDMI-0", "Fake HDMI output #0"); + auto data = conf.getOutputConfigs("HDMI-0", "Fake HDMI output #0", 0, 0); REQUIRE(data.size() == 2); REQUIRE(data[0]["layer"].asString() == "bottom"); REQUIRE(data[0]["height"].asInt() == 20); @@ -52,7 +52,7 @@ TEST_CASE("Load config with multiple bars", "[config]") { REQUIRE(data[1]["height"].asInt() == 23); } SECTION("select single config by output description") { - auto data = conf.getOutputConfigs("HDMI-1", "Fake HDMI output #1"); + auto data = conf.getOutputConfigs("HDMI-1", "Fake HDMI output #1", 0, 0); REQUIRE(data.size() == 1); REQUIRE(data[0]["layer"].asString() == "overlay"); REQUIRE(data[0]["position"].asString() == "left"); @@ -75,11 +75,11 @@ TEST_CASE("Load simple config with include", "[config]") { REQUIRE((data.isMember("nullOption") && data["nullOption"].isNull())); } SECTION("select configs for configured output") { - auto configs = conf.getOutputConfigs("HDMI-0", "Fake HDMI output #0"); + auto configs = conf.getOutputConfigs("HDMI-0", "Fake HDMI output #0", 0, 0); REQUIRE(configs.size() == 1); } SECTION("select configs for missing output") { - auto configs = conf.getOutputConfigs("HDMI-1", "Fake HDMI output #1"); + auto configs = conf.getOutputConfigs("HDMI-1", "Fake HDMI output #1", 0, 0); REQUIRE(configs.empty()); } } @@ -116,25 +116,25 @@ TEST_CASE("Load multiple bar config with include", "[config]") { conf.load("test/config/include-multi.json"); SECTION("bar config with sole include") { - auto data = conf.getOutputConfigs("OUT-0", "Fake output #0"); + auto data = conf.getOutputConfigs("OUT-0", "Fake output #0", 0, 0); REQUIRE(data.size() == 1); REQUIRE(data[0]["height"].asInt() == 20); } SECTION("bar config with output and include") { - auto data = conf.getOutputConfigs("OUT-1", "Fake output #1"); + auto data = conf.getOutputConfigs("OUT-1", "Fake output #1", 0, 0); REQUIRE(data.size() == 1); REQUIRE(data[0]["height"].asInt() == 21); } SECTION("bar config with output override") { - auto data = conf.getOutputConfigs("OUT-2", "Fake output #2"); + auto data = conf.getOutputConfigs("OUT-2", "Fake output #2", 0, 0); REQUIRE(data.size() == 1); REQUIRE(data[0]["height"].asInt() == 22); } SECTION("multiple levels of include") { - auto data = conf.getOutputConfigs("OUT-3", "Fake output #3"); + auto data = conf.getOutputConfigs("OUT-3", "Fake output #3", 0, 0); REQUIRE(data.size() == 1); REQUIRE(data[0]["height"].asInt() == 23); }