diff --git a/inc/head.h b/inc/head.h index 9ca55c3..71951a1 100644 --- a/inc/head.h +++ b/inc/head.h @@ -60,8 +60,13 @@ bool head_matches_name_desc_exact(const void *head, const void *name_desc); bool head_matches_name_desc_regex(const void *head, const void *name_desc); +bool head_matches_name_desc_fuzzy(const void *h, const void *name_desc); + bool head_matches_name_desc_partial(const void *head, const void *name_desc); -bool head_name_desc_partial_matches_head(const void *name_desc, const void *head); + +bool head_matches_name_desc(const void *head, const void *name_desc); + +bool head_name_desc_matches_head(const void *name_desc, const void *head); wl_fixed_t head_auto_scale(struct Head *head); diff --git a/inc/mode.h b/inc/mode.h index 5e058ad..385c3ee 100644 --- a/inc/mode.h +++ b/inc/mode.h @@ -25,6 +25,10 @@ struct ModesResRefresh { struct SList *modes; }; +struct Mode *mode_preferred(struct Head *head); + +struct Mode *mode_max_preferred(struct Head *head); + int32_t mhz_to_hz(int32_t mhz); double mode_dpi(struct Mode *mode); diff --git a/src/head.c b/src/head.c index ef9f57a..2042d32 100644 --- a/src/head.c +++ b/src/head.c @@ -23,7 +23,7 @@ bool head_is_max_preferred_refresh(struct Head *head) { return false; for (struct SList *i = cfg->max_preferred_refresh_name_desc; i; i = i->nex) { - if (head_matches_name_desc_partial(head, i->val)) { + if (head_matches_name_desc(head, i->val)) { return true; } } @@ -31,56 +31,7 @@ bool head_is_max_preferred_refresh(struct Head *head) { } bool head_matches_user_mode(const void *user_mode, const void *head) { - return user_mode && head && head_matches_name_desc_partial((struct Head*)head, ((struct UserMode*)user_mode)->name_desc); -} - -struct Mode *preferred_mode(struct Head *head) { - if (!head) - return NULL; - - struct Mode *mode = NULL; - for (struct SList *i = head->modes; i; i = i->nex) { - if (!i->val) - continue; - mode = i->val; - - if (mode->preferred && !slist_find_equal(head->modes_failed, NULL, mode)) { - return mode; - } - } - - return NULL; -} - -struct Mode *max_preferred_mode(struct Head *head) { - struct Mode *preferred = preferred_mode(head); - - if (!preferred) - return NULL; - - struct Mode *mode = NULL, *max = NULL; - - for (struct SList *i = head->modes; i; i = i->nex) { - if (!i->val) - continue; - mode = i->val; - - if (slist_find_equal(head->modes_failed, NULL, mode)) { - continue; - } - - if (mode->width != preferred->width || mode->height != preferred->height) { - continue; - } - - if (!max) { - max = mode; - } else if (mode->refresh_mhz > max->refresh_mhz) { - max = mode; - } - } - - return max; + return user_mode && head && head_matches_name_desc((struct Head*)head, ((struct UserMode*)user_mode)->name_desc); } struct Mode *max_mode(struct Head *head) { @@ -155,7 +106,7 @@ bool head_matches_name_desc_regex(const void *h, const void *n) { return !result; } -bool head_matches_name_desc_partial(const void *h, const void *n) { +bool head_matches_name_desc_fuzzy(const void *h, const void *n) { const struct Head *head = h; const char *name_desc = n; @@ -168,8 +119,14 @@ bool head_matches_name_desc_partial(const void *h, const void *n) { ); } -bool head_name_desc_partial_matches_head(const void *n, const void *h) { - return head_matches_name_desc_partial(h, n); +bool head_matches_name_desc(const void *h, const void *n) { + return head_matches_name_desc_exact(h, n) || + head_matches_name_desc_regex(h, n) || + head_matches_name_desc_fuzzy(h, n); +} + +bool head_name_desc_matches_head(const void *n, const void *h) { + return head_matches_name_desc(h, n); } bool head_matches_name_desc_exact(const void *h, const void *n) { @@ -244,9 +201,9 @@ struct Mode *head_find_mode(struct Head *head) { // always preferred if (!mode) { if (head_is_max_preferred_refresh(head)) { - mode = max_preferred_mode(head); + mode = mode_max_preferred(head); } else { - mode = preferred_mode(head); + mode = mode_preferred(head); } if (!mode && !head->warned_no_preferred) { head->warned_no_preferred = true; @@ -254,7 +211,7 @@ struct Mode *head_find_mode(struct Head *head) { } } - // last change maximum + // last chance maximum if (!mode) { mode = max_mode(head); } diff --git a/src/layout.c b/src/layout.c index be55801..9e42ab8 100644 --- a/src/layout.c +++ b/src/layout.c @@ -116,10 +116,10 @@ struct SList *order_heads(struct SList *order_name_desc, struct SList *heads) { i++; } - // partial + // fuzzy i = 0; for (struct SList *o = order_name_desc; o; o = o->nex) { - slist_move(&order_heads[i], &sorting, head_matches_name_desc_partial, o->val); + slist_move(&order_heads[i], &sorting, head_matches_name_desc_fuzzy, o->val); i++; } @@ -153,7 +153,7 @@ void desire_enabled(struct Head *head) { head->desired.enabled |= slist_length(heads) == 1; // explicitly disabled - head->desired.enabled &= slist_find_equal(cfg->disabled_name_desc, head_name_desc_partial_matches_head, head) == NULL; + head->desired.enabled &= slist_find_equal(cfg->disabled_name_desc, head_name_desc_matches_head, head) == NULL; } void desire_mode(struct Head *head) { @@ -191,7 +191,7 @@ void desire_scale(struct Head *head) { struct UserScale *user_scale; for (struct SList *i = cfg->user_scales; i; i = i->nex) { user_scale = (struct UserScale*)i->val; - if (head_matches_name_desc_partial(head, user_scale->name_desc)) { + if (head_matches_name_desc(head, user_scale->name_desc)) { head->desired.scale = wl_fixed_from_double(user_scale->scale); return; } diff --git a/src/marshalling.cpp b/src/marshalling.cpp index 0225c6d..d50e537 100644 --- a/src/marshalling.cpp +++ b/src/marshalling.cpp @@ -31,6 +31,23 @@ extern "C" { #include "server.h" } +// If this is a regex pattern, attempt to compile it before including it in configuration. +bool validate_regex(const char *pattern, enum CfgElement element) { + bool rc = true; + if (pattern[0] == '!') { + regex_t regex; + int result = regcomp(®ex, pattern + 1, REG_EXTENDED); + if (result) { + char err[1024]; + regerror(result, ®ex, err, 1024); + log_warn("Ignoring bad %s regex '%s': %s", cfg_element_name(element), pattern + 1, err); + rc = false; + } + regfree(®ex); + } + return rc; +} + bool parse_node_val_bool(const YAML::Node &node, const char *key, bool *val, const char *desc1, const char *desc2) { if (node[key]) { try { @@ -264,20 +281,8 @@ void cfg_parse_node(struct Cfg *cfg, const YAML::Node &node) { const std::string &order_str = order.as(); const char *order_cstr = order_str.c_str(); if (!slist_find_equal(cfg->order_name_desc, slist_equal_strcmp, order_cstr)) { - // If this is a regex pattern, attempt to compile it before - // including it in order configuration. - if (order_cstr[0] == '!') { - regex_t regex; - int result = regcomp(®ex, order_cstr + 1, REG_EXTENDED); - if (result) { - char err[1024]; - regerror(result, ®ex, err, 1024); - log_warn("\nCould not compile ORDER regex '%s': %s", order_cstr + 1, err); - regfree(®ex); - continue; - } else { - regfree(®ex); - } + if (!validate_regex(order_cstr, ORDER)) { + continue; } slist_append(&cfg->order_name_desc, strdup(order_cstr)); } @@ -321,6 +326,10 @@ void cfg_parse_node(struct Cfg *cfg, const YAML::Node &node) { cfg_user_scale_free(user_scale); continue; } + if (!validate_regex(user_scale->name_desc, SCALE)) { + cfg_user_mode_free(user_scale); + continue; + } if (!parse_node_val_float(scale, "SCALE", &user_scale->scale, "SCALE", user_scale->name_desc)) { cfg_user_scale_free(user_scale); continue; @@ -339,6 +348,10 @@ void cfg_parse_node(struct Cfg *cfg, const YAML::Node &node) { cfg_user_mode_free(user_mode); continue; } + if (!validate_regex(user_mode->name_desc, MODE)) { + cfg_user_mode_free(user_mode); + continue; + } if (mode["MAX"] && !parse_node_val_bool(mode, "MAX", &user_mode->max, "MODE", user_mode->name_desc)) { cfg_user_mode_free(user_mode); continue; @@ -362,21 +375,29 @@ void cfg_parse_node(struct Cfg *cfg, const YAML::Node &node) { } if (node["MAX_PREFERRED_REFRESH"]) { - const auto &name_desc = node["MAX_PREFERRED_REFRESH"]; - for (const auto &name_desc : name_desc) { - const std::string &name_desc_str = name_desc.as(); - if (!slist_find_equal(cfg->max_preferred_refresh_name_desc, slist_equal_strcmp, name_desc_str.c_str())) { - slist_append(&cfg->max_preferred_refresh_name_desc, strdup(name_desc_str.c_str())); + const auto &maxes = node["MAX_PREFERRED_REFRESH"]; + for (const auto &max : maxes) { + const std::string &max_str = max.as(); + const char *max_cstr = max_str.c_str(); + if (!slist_find_equal(cfg->max_preferred_refresh_name_desc, slist_equal_strcmp, max_cstr)) { + if (!validate_regex(max_cstr, MAX_PREFERRED_REFRESH)) { + continue; + } + slist_append(&cfg->max_preferred_refresh_name_desc, strdup(max_cstr)); } } } if (node["DISABLED"]) { - const auto &name_desc = node["DISABLED"]; - for (const auto &name_desc : name_desc) { - const std::string &name_desc_str = name_desc.as(); - if (!slist_find_equal(cfg->disabled_name_desc, slist_equal_strcmp, name_desc_str.c_str())) { - slist_append(&cfg->disabled_name_desc, strdup(name_desc_str.c_str())); + const auto &disableds = node["DISABLED"]; + for (const auto &disabled : disableds) { + const std::string &disabled_str = disabled.as(); + const char *disabled_cstr = disabled_str.c_str(); + if (!slist_find_equal(cfg->disabled_name_desc, slist_equal_strcmp, disabled_cstr)) { + if (!validate_regex(disabled_cstr, DISABLED)) { + continue; + } + slist_append(&cfg->disabled_name_desc, strdup(disabled_cstr)); } } } diff --git a/src/mode.c b/src/mode.c index ff720e5..fe4e39b 100644 --- a/src/mode.c +++ b/src/mode.c @@ -8,6 +8,55 @@ #include "head.h" #include "list.h" +struct Mode *mode_preferred(struct Head *head) { + if (!head) + return NULL; + + struct Mode *mode = NULL; + for (struct SList *i = head->modes; i; i = i->nex) { + if (!i->val) + continue; + mode = i->val; + + if (mode->preferred && !slist_find_equal(head->modes_failed, NULL, mode)) { + return mode; + } + } + + return NULL; +} + +struct Mode *mode_max_preferred(struct Head *head) { + struct Mode *preferred = mode_preferred(head); + + if (!preferred) + return NULL; + + struct Mode *mode = NULL, *max = NULL; + + for (struct SList *i = head->modes; i; i = i->nex) { + if (!i->val) + continue; + mode = i->val; + + if (slist_find_equal(head->modes_failed, NULL, mode)) { + continue; + } + + if (mode->width != preferred->width || mode->height != preferred->height) { + continue; + } + + if (!max) { + max = mode; + } else if (mode->refresh_mhz > max->refresh_mhz) { + max = mode; + } + } + + return max; +} + int32_t mhz_to_hz(int32_t mhz) { return (mhz + 500) / 1000; } diff --git a/tst/GNUmakefile b/tst/GNUmakefile index 2e64e59..e247d85 100644 --- a/tst/GNUmakefile +++ b/tst/GNUmakefile @@ -21,7 +21,7 @@ tst-cli: tst/tst-cli.o $(OBJS) $(CXX) -o $(@) $(^) $(LDFLAGS) $(LDLIBS) $(WRAPS) tst-head: tst/tst-head.o $(OBJS) - $(CXX) -o $(@) $(^) $(LDFLAGS) $(LDLIBS) $(WRAPS),--wrap=mode_dpi,--wrap=mode_user_mode + $(CXX) -o $(@) $(^) $(LDFLAGS) $(LDLIBS) $(WRAPS),--wrap=mode_dpi,--wrap=mode_user_mode,--wrap=mode_max_preferred tst-layout: tst/tst-layout.o $(OBJS) $(CXX) -o $(@) $(^) $(LDFLAGS) $(LDLIBS) $(WRAPS),--wrap=lid_is_closed,--wrap=head_find_mode,--wrap=head_auto_scale diff --git a/tst/marshalling/cfg-bad.yaml b/tst/marshalling/cfg-bad.yaml index 7b09c05..4a3b03a 100644 --- a/tst/marshalling/cfg-bad.yaml +++ b/tst/marshalling/cfg-bad.yaml @@ -3,12 +3,13 @@ ARRANGE: BAD_ARRANGE ALIGN: BAD_ALIGN AUTO_SCALE: BAD_AUTO_SCALE ORDER: - - '!(' + - '!(order' SCALE: - - NAME_DESC: BAD_SCALE_NAME SCALE: BAD_SCALE_VAL - NAME_DESC: MISSING_SCALE_VALUE + - NAME_DESC: '!(scale' MODE: - - NAME_DESC: BAD_MODE_MAX @@ -19,4 +20,9 @@ MODE: HEIGHT: BAD_HEIGHT - NAME_DESC: BAD_MODE_HZ HZ: BAD_HZ + - NAME_DESC: '!(mode' +MAX_PREFERRED_REFRESH: + - '!(max' +DISABLED: + - '!(disabled' diff --git a/tst/tst-head.c b/tst/tst-head.c index ceb59b3..025ee23 100644 --- a/tst/tst-head.c +++ b/tst/tst-head.c @@ -27,6 +27,11 @@ struct Mode *__wrap_mode_user_mode(struct SList *modes, struct SList *modes_fail return (struct Mode *)mock(); } +struct Mode *__wrap_mode_max_preferred(struct Head *head) { + check_expected(head); + return (struct Mode *)mock(); +} + int before_all(void **state) { return 0; @@ -143,7 +148,7 @@ void head_find_mode__user_available(void **state) { // user preferred head struct UserMode *user_mode = cfg_user_mode_default(); - user_mode->name_desc = strdup("HEAD"); + user_mode->name_desc = strdup("!.*EAD"); slist_append(&cfg->user_modes, user_mode); head.name = strdup("HEAD"); @@ -164,7 +169,7 @@ void head_find_mode__user_failed(void **state) { // user preferred head struct UserMode *user_mode = cfg_user_mode_default(); - user_mode->name_desc = strdup("HEAD"); + user_mode->name_desc = strdup("!HEA.*"); slist_append(&cfg->user_modes, user_mode); head.name = strdup("HEAD"); @@ -191,6 +196,29 @@ void head_find_mode__user_failed(void **state) { assert_ptr_equal(head_find_mode(&head), &mode); } +void head_find_mode__preferred(void **state) { + struct Head head = { .name = "name", }; + struct Mode mode = { .preferred = true, }; + + slist_append(&head.modes, &mode); + + assert_ptr_equal(head_find_mode(&head), &mode); +} + +void head_find_mode__max_preferred_refresh(void **state) { + struct Head head = { .name = "name", }; + struct Mode mode = { 0 }; + + slist_append(&cfg->max_preferred_refresh_name_desc, strdup("!nam.*")); + + slist_append(&head.modes, &mode); + + expect_value(__wrap_mode_max_preferred, head, &head); + will_return(__wrap_mode_max_preferred, &mode); + + assert_ptr_equal(head_find_mode(&head), &mode); +} + void head_find_mode__max(void **state) { struct Head head = { .name = "name", }; struct Mode mode = { 0 }; @@ -217,6 +245,8 @@ int main(void) { TEST(head_find_mode__none), TEST(head_find_mode__user_available), TEST(head_find_mode__user_failed), + TEST(head_find_mode__preferred), + TEST(head_find_mode__max_preferred_refresh), TEST(head_find_mode__max), }; diff --git a/tst/tst-layout.c b/tst/tst-layout.c index c265c98..a51a280 100644 --- a/tst/tst-layout.c +++ b/tst/tst-layout.c @@ -341,7 +341,7 @@ void desire_enabled__lid_closed_one_disabled(void **state) { }; slist_append(&heads, &head0); - slist_append(&cfg->disabled_name_desc, strdup("head0")); + slist_append(&cfg->disabled_name_desc, strdup("![hH]ead[0-9]")); expect_string(__wrap_lid_is_closed, name, "head0"); will_return(__wrap_lid_is_closed, true); @@ -466,7 +466,7 @@ void desire_scale__user(void **state) { .desired.enabled = true, }; - slist_append(&cfg->user_scales, cfg_user_scale_init("head0", 3.5)); + slist_append(&cfg->user_scales, cfg_user_scale_init("![Hh]ea.*", 3.5)); slist_append(&cfg->user_scales, cfg_user_scale_init("head1", 7.5)); desire_scale(&head0); diff --git a/tst/tst-marshalling.c b/tst/tst-marshalling.c index 74951e6..c27a823 100644 --- a/tst/tst-marshalling.c +++ b/tst/tst-marshalling.c @@ -138,7 +138,7 @@ void unmarshal_cfg_from_file__bad(void **state) { expect_log_warn("Ignoring invalid LOG_THRESHOLD %s, using default %s", "BAD_LOG_THRESHOLD", "INFO", NULL, NULL); - expect_log_warn("\nCould not compile ORDER regex '%s': %s", "(", NULL, NULL, NULL); + expect_log_warn("Ignoring bad %s regex '%s': %s", "ORDER", "(order", NULL, NULL); expect_log_warn("Ignoring invalid ARRANGE %s, using default %s", "BAD_ARRANGE", "ROW", NULL, NULL); @@ -152,6 +152,8 @@ void unmarshal_cfg_from_file__bad(void **state) { expect_log_warn("Ignoring missing %s %s %s", "SCALE", "MISSING_SCALE_VALUE", "SCALE", NULL); + expect_log_warn("Ignoring bad %s regex '%s': %s", "SCALE", "(scale", NULL, NULL); + expect_log_warn("Ignoring missing %s %s %s", "MODE", "", "NAME_DESC", NULL); expect_log_warn("Ignoring invalid %s %s %s %s", "MODE", "BAD_MODE_MAX", "MAX", "BAD_MAX"); @@ -162,6 +164,12 @@ void unmarshal_cfg_from_file__bad(void **state) { expect_log_warn("Ignoring invalid %s %s %s %s", "MODE", "BAD_MODE_HZ", "HZ", "BAD_HZ"); + expect_log_warn("Ignoring bad %s regex '%s': %s", "MODE", "(mode", NULL, NULL); + + expect_log_warn("Ignoring bad %s regex '%s': %s", "MAX_PREFERRED_REFRESH", "(max", NULL, NULL); + + expect_log_warn("Ignoring bad %s regex '%s': %s", "DISABLED", "(disabled", NULL, NULL); + assert_true(unmarshal_cfg_from_file(read)); struct Cfg *expected = cfg_default();