diff --git a/src/config/Config.h b/src/config/Config.h index e77620f3..205d9e48 100644 --- a/src/config/Config.h +++ b/src/config/Config.h @@ -4,6 +4,7 @@ #include struct Context { + bool system_filter_matched; std::string window_class_filter; std::string window_title_filter; }; diff --git a/src/config/ParseConfig.cpp b/src/config/ParseConfig.cpp index 3e64710b..e02bdb7c 100644 --- a/src/config/ParseConfig.cpp +++ b/src/config/ParseConfig.cpp @@ -65,16 +65,18 @@ namespace { } } // namespace -Config ParseConfig::operator()(std::istream& is) { +Config ParseConfig::operator()(std::istream& is, bool add_default_mappings) { m_line_no = 0; m_config.commands.clear(); m_config.contexts.clear(); m_commands_mapped.clear(); m_macros.clear(); - // automatically add mappings for common modifiers - for (auto key : { Key::Shift, Key::Control, Key::AltLeft, Key::AltRight }) - add_mapping( { { *key, KeyState::Down } }, { { *key, KeyState::Down } }); + if (add_default_mappings) { + // add mappings for immediately passing on common modifiers + for (auto key : { Key::Shift, Key::Control, Key::AltLeft, Key::AltRight }) + add_mapping( { { *key, KeyState::Down } }, { { *key, KeyState::Down } }); + } auto line = std::string(); while (is.good()) { @@ -88,6 +90,33 @@ Config ParseConfig::operator()(std::istream& is) { if (!kv.second) throw ParseError("command '" + kv.first + "' was not mapped"); + // remove contexts of other systems + // and apply contexts without class and title filter immediately + auto context_index = 0; + for (auto it = begin(m_config.contexts); it != end(m_config.contexts); ++context_index) { + auto& context = *it; + if (!context.system_filter_matched || + (context.window_class_filter.empty() && + context.window_title_filter.empty())) { + for (auto& command : m_config.commands) { + auto& mappings = command.context_mappings; + const auto mapping = std::find_if(cbegin(mappings), cend(mappings), + [&](const ContextMapping& mapping) { + return (mapping.context_index == context_index); + }); + if (mapping != cend(mappings)) { + if (context.system_filter_matched) + command.default_mapping = mapping->output; + mappings.erase(mapping); + } + } + it = m_config.contexts.erase(it); + } + else { + ++it; + } + } + replace_logical_modifiers(*Key::Shift, *Key::ShiftLeft, *Key::ShiftRight); replace_logical_modifiers(*Key::Control, *Key::ControlLeft, *Key::ControlRight); replace_logical_modifiers(*Key::Meta, *Key::MetaLeft, *Key::MetaRight); @@ -154,12 +183,16 @@ void ParseConfig::parse_context(It it, const It end) { auto title_filter = std::string(); auto system_filter_matched = true; - while (it != end) { + do { const auto attrib = read_ident(&it, end); + if (attrib.empty()) + error("identifier expected"); + skip_space(&it, end); if (!skip(&it, end, "=")) error("missing '='"); + skip_space(&it, end); auto value = read_value(&it, end); if (attrib == "class") { class_filter = std::move(value); @@ -175,13 +208,13 @@ void ParseConfig::parse_context(It it, const It end) { } skip_space(&it, end); } + while (it != end); - // simply set invalid class when system filter did not match - if (!system_filter_matched) - class_filter = "$"; - - m_config.contexts.push_back( - { std::move(class_filter), std::move(title_filter) }); + m_config.contexts.push_back({ + system_filter_matched, + std::move(class_filter), + std::move(title_filter) + }); } void ParseConfig::parse_mapping(std::string name, It begin, It end) { diff --git a/src/config/ParseConfig.h b/src/config/ParseConfig.h index 47ec198a..868d513c 100644 --- a/src/config/ParseConfig.h +++ b/src/config/ParseConfig.h @@ -8,7 +8,7 @@ class ParseConfig { public: - Config operator()(std::istream& is); + Config operator()(std::istream& is, bool add_default_mappings = true); private: using It = std::string::const_iterator; diff --git a/src/test/test1_ParseConfig.cpp b/src/test/test1_ParseConfig.cpp index d45077ae..9fb2f66f 100644 --- a/src/test/test1_ParseConfig.cpp +++ b/src/test/test1_ParseConfig.cpp @@ -6,7 +6,7 @@ namespace { Config parse_config(const char* config) { static auto parse = ParseConfig(); auto stream = std::stringstream(config); - return parse(stream); + return parse(stream, false); } } // namespace @@ -23,11 +23,11 @@ TEST_CASE("Valid config", "[ParseConfig]") { E >> CommandB # comment - [window class='test' title=test] # comment + [ system = "Windows" class='test'title=test ] # comment CommandA >> Y # comment CommandB >> MyMacro # comment - [window system='Linux'] # comment + [system='Linux'] # comment CommandA >> Shift{Y} # comment CommandB >> Shift{MyMacro} # comment )"; @@ -75,9 +75,18 @@ TEST_CASE("Problems", "[ParseConfig]") { )"; REQUIRE_THROWS(parse_config(string)); + // empty declarative + string = R"( + C >> CommandA + + [] + CommandA >> D + )"; + REQUIRE_THROWS(parse_config(string)); + // mapping not defined command string = R"( - [window class=''] + [class=''] CommandB >> D )"; REQUIRE_THROWS(parse_config(string)); @@ -86,7 +95,7 @@ TEST_CASE("Problems", "[ParseConfig]") { string = R"( C >> CommandA - [window class=''] + [class=''] CommandA >> D CommandA >> E )"; @@ -94,14 +103,14 @@ TEST_CASE("Problems", "[ParseConfig]") { // mapping sequence in context string = R"( - [window class='abc'] + [class='abc'] C >> D )"; REQUIRE_THROWS(parse_config(string)); // defining command in context string = R"( - [window class='abc'] + [class='abc'] C >> CommandA )"; REQUIRE_THROWS(parse_config(string)); @@ -110,7 +119,7 @@ TEST_CASE("Problems", "[ParseConfig]") { string = R"( C >> CommandA - [window class=''] + [class=''] CommandA >> D )"; REQUIRE_NOTHROW(parse_config(string)); @@ -118,6 +127,39 @@ TEST_CASE("Problems", "[ParseConfig]") { //-------------------------------------------------------------------- +TEST_CASE("System contexts", "[ParseConfig]") { + auto string = R"( + A >> B + B >> command + + [system="Linux"] + command >> L + + [system="Linux" title="app"] + command >> X + + [system="Windows"] + command >> W + + [system="Windows" title="app"] + command >> Y + )"; + auto config = parse_config(string); + REQUIRE(config.contexts.size() == 1); + auto commands = config.commands; + REQUIRE(commands.size() == 2); + REQUIRE(commands[0].context_mappings.empty()); + REQUIRE(commands[1].context_mappings.size() == 1); + REQUIRE(format_sequence(commands[0].default_mapping) == "+B"); +#if defined(__linux__) + REQUIRE(format_sequence(commands[1].default_mapping) == "+L"); +#else + REQUIRE(format_sequence(commands[1].default_mapping) == "+W"); +#endif +} + +//-------------------------------------------------------------------- + TEST_CASE("Macros", "[ParseConfig]") { // correct auto string = R"(