From b8d813d53616b858b96ba6d4e6d4620feda3c71a Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Thu, 17 Oct 2024 03:28:52 +0100 Subject: [PATCH] config: pop a message box for problematic options Signed-off-by: Yuxuan Shui --- src/c2.c | 59 ++++++++------- src/c2.h | 5 +- src/config.h | 33 ++++++++- src/config_libconfig.c | 164 ++++++++++++++++++++++------------------- src/log.h | 11 +-- src/options.c | 31 ++++---- src/picom.c | 69 +++++++++++++++++ 7 files changed, 246 insertions(+), 126 deletions(-) diff --git a/src/c2.c b/src/c2.c index ab6ac377f8..e420b3e331 100644 --- a/src/c2.c +++ b/src/c2.c @@ -54,7 +54,7 @@ typedef struct { struct c2_condition_node_branch *b; struct c2_condition_node_leaf *l; }; - bool neg; + bool neg : 1; } c2_condition_node_ptr; struct c2_tracked_property_key { @@ -150,10 +150,10 @@ struct c2_condition_node_leaf { C2_L_MPCRE, } match : 3; bool match_ignorecase : 1; + bool target_on_client : 1; char *tgt; unsigned int target_id; xcb_atom_t tgtatom; - bool target_on_client; int index; // TODO(yshui) translate some of the pre-defined targets to // generic window properties. e.g. `name = "xterm"` @@ -324,8 +324,8 @@ static inline int c2h_b_opcmp(c2_b_op_t op1, c2_b_op_t op2) { return c2h_b_opp(op1) - c2h_b_opp(op2); } -static int -c2_parse_group(const char *pattern, int offset, c2_condition_node_ptr *presult, int level); +static int c2_parse_group(const char *pattern, int offset, c2_condition_node_ptr *presult, + int level, bool *deprecated); static int c2_parse_target(const char *pattern, int offset, c2_condition_node_ptr *presult); static int c2_parse_op(const char *pattern, int offset, c2_condition_node_ptr *presult); static int c2_parse_pattern(const char *pattern, int offset, c2_condition_node_ptr *presult); @@ -349,7 +349,8 @@ static inline void c2_freep(c2_condition_node_ptr *pp) { /** * Parse a condition string. */ -struct c2_condition *c2_parse(struct list_node *list, const char *pattern, void *data) { +struct c2_condition * +c2_parse(struct list_node *list, const char *pattern, void *data, bool *deprecated) { if (!pattern) { return NULL; } @@ -361,7 +362,7 @@ struct c2_condition *c2_parse(struct list_node *list, const char *pattern, void if (strlen(pattern) >= 2 && ':' == pattern[1]) { offset = c2_parse_legacy(pattern, 0, &result); } else { - offset = c2_parse_group(pattern, 0, &result, 0); + offset = c2_parse_group(pattern, 0, &result, 0, deprecated); } if (offset < 0) { @@ -397,13 +398,13 @@ struct c2_condition *c2_parse(struct list_node *list, const char *pattern, void c2_condition * c2_parse_with_prefix(struct list_node *list, const char *pattern, void *(*parse_prefix)(const char *input, const char **end, void *), - void (*free_value)(void *), void *user_data) { + void (*free_value)(void *), void *user_data, bool *deprecated) { char *pattern_start = NULL; void *val = parse_prefix(pattern, (const char **)&pattern_start, user_data); if (pattern_start == NULL) { return NULL; } - auto ret = c2_parse(list, pattern_start, val); + auto ret = c2_parse(list, pattern_start, val, deprecated); if (!ret && free_value) { free_value(val); } @@ -412,7 +413,8 @@ c2_parse_with_prefix(struct list_node *list, const char *pattern, TEST_CASE(c2_parse) { char str[1024]; - struct c2_condition *cond = c2_parse(NULL, "name = \"xterm\"", NULL); + bool deprecated = false; + struct c2_condition *cond = c2_parse(NULL, "name = \"xterm\"", NULL, &deprecated); struct atom *atoms = init_mock_atoms(); struct c2_state *state = c2_state_new(atoms); TEST_NOTEQUAL(cond, NULL); @@ -439,16 +441,16 @@ TEST_CASE(c2_parse) { destroy_atoms(atoms); c2_free_condition(cond, NULL); - cond = c2_parse(NULL, "argb", NULL); + cond = c2_parse(NULL, "argb", NULL, &deprecated); TEST_NOTEQUAL(cond, NULL); TEST_NOTEQUAL(cond->root.type, C2_NODE_TYPE_BRANCH); TEST_EQUAL(cond->root.l->ptntype, C2_L_PTINT); c2_free_condition(cond, NULL); - cond = c2_parse(NULL, "argb = 'b'", NULL); + cond = c2_parse(NULL, "argb = 'b'", NULL, &deprecated); TEST_EQUAL(cond, NULL); - cond = c2_parse(NULL, "_GTK_FRAME_EXTENTS@:c", NULL); + cond = c2_parse(NULL, "_GTK_FRAME_EXTENTS@:c", NULL, &deprecated); TEST_NOTEQUAL(cond, NULL); TEST_NOTEQUAL(cond->root.type, C2_NODE_TYPE_BRANCH); TEST_NOTEQUAL(cond->root.l, NULL); @@ -474,14 +476,14 @@ TEST_CASE(c2_parse) { TEST_STREQUAL3(str, "_GTK_FRAME_EXTENTS@[0]", len); c2_free_condition(cond, NULL); - cond = c2_parse( - NULL, "!(name != \"xterm\" && class_g *= \"XTerm\") || !name != \"yterm\"", NULL); + cond = c2_parse(NULL, "!(name != \"xterm\" && class_g *= \"XTerm\") || !name != \"yterm\"", + NULL, &deprecated); TEST_NOTEQUAL(cond, NULL); TEST_STREQUAL(c2_condition_to_str(cond), "(!(name != \"xterm\" && class_g *= " "\"XTerm\") || name = \"yterm\")"); c2_free_condition(cond, NULL); - cond = c2_parse(NULL, "name = \"xterm\" && class_g *= \"XTerm\"", NULL); + cond = c2_parse(NULL, "name = \"xterm\" && class_g *= \"XTerm\"", NULL, &deprecated); TEST_NOTEQUAL(cond, NULL); TEST_EQUAL(cond->root.type, C2_NODE_TYPE_BRANCH); TEST_NOTEQUAL(cond->root.b, NULL); @@ -511,7 +513,8 @@ TEST_CASE(c2_parse) { c2_state_free(state); destroy_atoms(atoms); - cond = c2_parse(NULL, "_NET_WM_STATE[1]:32a *='_NET_WM_STATE_HIDDEN'", NULL); + cond = c2_parse(NULL, "_NET_WM_STATE[1]:32a *='_NET_WM_STATE_HIDDEN'", NULL, + &deprecated); TEST_EQUAL(cond->root.l->index, 1); TEST_STREQUAL(cond->root.l->tgt, "_NET_WM_STATE"); TEST_STREQUAL(cond->root.l->ptnstr, "_NET_WM_STATE_HIDDEN"); @@ -520,39 +523,39 @@ TEST_CASE(c2_parse) { TEST_STREQUAL3(str, "_NET_WM_STATE[1] *= \"_NET_WM_STATE_HIDDEN\"", len); c2_free_condition(cond, NULL); - cond = c2_parse(NULL, "_NET_WM_STATE[*]:32a*='_NET_WM_STATE_HIDDEN'", NULL); + cond = c2_parse(NULL, "_NET_WM_STATE[*]:32a*='_NET_WM_STATE_HIDDEN'", NULL, &deprecated); TEST_EQUAL(cond->root.l->index, -1); len = c2_condition_node_to_str(cond->root, str, sizeof(str)); TEST_STREQUAL3(str, "_NET_WM_STATE[*] *= \"_NET_WM_STATE_HIDDEN\"", len); c2_free_condition(cond, NULL); - cond = c2_parse(NULL, "!class_i:0s", NULL); + cond = c2_parse(NULL, "!class_i:0s", NULL, &deprecated); TEST_NOTEQUAL(cond, NULL); len = c2_condition_node_to_str(cond->root, str, sizeof(str)); TEST_STREQUAL3(str, "!class_i", len); c2_free_condition(cond, NULL); - cond = c2_parse(NULL, "_NET_WM_STATE = '_NET_WM_STATE_HIDDEN'", NULL); + cond = c2_parse(NULL, "_NET_WM_STATE = '_NET_WM_STATE_HIDDEN'", NULL, &deprecated); TEST_NOTEQUAL(cond, NULL); c2_free_condition(cond, NULL); - cond = c2_parse(NULL, "1A:\n1111111111111ar1", NULL); + cond = c2_parse(NULL, "1A:\n1111111111111ar1", NULL, &deprecated); TEST_EQUAL(cond, NULL); - cond = c2_parse(NULL, "N [4444444444444: \n", NULL); + cond = c2_parse(NULL, "N [4444444444444: \n", NULL, &deprecated); TEST_EQUAL(cond, NULL); - cond = c2_parse(NULL, " x:a=\"b\377\\xCCCCC", NULL); + cond = c2_parse(NULL, " x:a=\"b\377\\xCCCCC", NULL, &deprecated); TEST_EQUAL(cond, NULL); - cond = c2_parse(NULL, "!!!!!!!((((((!(((((,", NULL); + cond = c2_parse(NULL, "!!!!!!!((((((!(((((,", NULL, &deprecated); TEST_EQUAL(cond, NULL); const char *rule = "(((role = \"\\\\tg^\\n\\n[\\t\" && role ~?= \"\") && " "role ~?= \"\\n\\n\\n\\b\\n^\\n*0bon\") && role ~?= " "\"\\n\\n\\x8a\\b\\n^\\n*0\\n[\\n[\\n\\n\\b\\n\")"; - cond = c2_parse(NULL, rule, NULL); + cond = c2_parse(NULL, rule, NULL, &deprecated); TEST_NOTEQUAL(cond, NULL); len = c2_condition_node_to_str(cond->root, str, sizeof(str)); TEST_STREQUAL3(str, rule, len); @@ -580,8 +583,8 @@ TEST_CASE(c2_parse) { * * @return offset of next character in string */ -static int -c2_parse_group(const char *pattern, int offset, c2_condition_node_ptr *presult, int level) { +static int c2_parse_group(const char *pattern, int offset, c2_condition_node_ptr *presult, + int level, bool *deprecated) { if (!pattern) { return -1; } @@ -687,7 +690,8 @@ c2_parse_group(const char *pattern, int offset, c2_condition_node_ptr *presult, // It's a subgroup if it starts with '(' if ('(' == pattern[offset]) { - if ((offset = c2_parse_group(pattern, offset + 1, pele, level + 1)) < 0) { + if ((offset = c2_parse_group(pattern, offset + 1, pele, level + 1, + deprecated)) < 0) { goto fail; } } else { @@ -716,6 +720,7 @@ c2_parse_group(const char *pattern, int offset, c2_condition_node_ptr *presult, "always fail. Offending pattern is " "\"%s\"", pele->l->tgt, pattern); + *deprecated = true; } if (pele->l->op == C2_L_OEXISTS) { pele->l->ptntype = predef_type; diff --git a/src/c2.h b/src/c2.h index ac42586583..6bcbec092d 100644 --- a/src/c2.h +++ b/src/c2.h @@ -24,7 +24,8 @@ struct win; struct list_node; typedef void (*c2_userdata_free)(void *); -struct c2_condition *c2_parse(struct list_node *list, const char *pattern, void *data); +struct c2_condition * +c2_parse(struct list_node *list, const char *pattern, void *data, bool *deprecated); /// Parse a condition that has a prefix. The prefix is parsed by `parse_prefix`. If /// `free_value` is not NULL, it will be called to free the value returned by @@ -32,7 +33,7 @@ struct c2_condition *c2_parse(struct list_node *list, const char *pattern, void c2_condition * c2_parse_with_prefix(struct list_node *list, const char *pattern, void *(*parse_prefix)(const char *input, const char **end, void *), - void (*free_value)(void *), void *user_data); + void (*free_value)(void *), void *user_data, bool *deprecated); void c2_free_condition(c2_condition *lp, c2_userdata_free f); diff --git a/src/config.h b/src/config.h index 881581d9d3..507d2f3391 100644 --- a/src/config.h +++ b/src/config.h @@ -12,6 +12,7 @@ #include #include #include +#include #include // for xcb_render_fixed_t, XXX #include #include @@ -217,8 +218,16 @@ struct window_options { struct win_script animations[ANIMATION_TRIGGER_COUNT]; }; +struct option_name { + UT_hash_handle hh; + const char *name; +}; + /// Structure representing all options. typedef struct options { + // === Deprecation === + struct option_name *problematic_options; + // === Config === /// Path to the config file char *config_file_path; @@ -429,6 +438,26 @@ typedef struct options { extern const char *const BACKEND_STRS[NUM_BKEND + 1]; bool load_plugin(const char *name, const char *include_dir); +static inline void record_problematic_option(struct options *opt, const char *name) { + struct option_name *record = calloc(1, sizeof(*record)); + record->name = name; + HASH_ADD_STR(opt->problematic_options, name, record); +} + +static inline void +report_deprecated_option(struct options *opt, const char *name, bool error) { + struct option_name *record = NULL; + HASH_FIND_STR(opt->problematic_options, name, record); + if (record != NULL) { + return; + } + enum log_level level = error ? LOG_LEVEL_ERROR : LOG_LEVEL_WARN; + LOG_(level, + "Option \"%s\" is deprecated, please remove it from your config file and/or " + "command line options.", + name); + record_problematic_option(opt, name); +} bool must_use parse_long(const char *, long *); bool must_use parse_int(const char *, int *); @@ -485,10 +514,12 @@ static inline bool parse_vsync(const char *str) { /// Generate animation script for legacy fading options void generate_fading_config(struct options *opt); -static inline void log_warn_both_style_of_rules(const char *option_name) { +static inline void log_warn_both_style_of_rules(struct options *opt, const char *option_name) { log_warn("Option \"%s\" is set along with \"rules\". \"rules\" will take " "precedence, and \"%s\" will have no effect.", option_name, option_name); + opt->has_both_style_of_rules = true; + record_problematic_option(opt, option_name); } enum animation_trigger parse_animation_trigger(const char *trigger); diff --git a/src/config_libconfig.c b/src/config_libconfig.c index 66fd96d2cb..bc1c096b6f 100644 --- a/src/config_libconfig.c +++ b/src/config_libconfig.c @@ -124,7 +124,8 @@ FILE *open_config_file(const char *cpath, char **ppath) { /** * Parse a condition list in configuration file. */ -bool must_use parse_cfg_condlst(struct list_node *list, const config_t *pcfg, const char *name) { +bool must_use parse_cfg_condlst(struct list_node *list, const config_t *pcfg, + const char *name, bool *deprecated) { config_setting_t *setting = config_lookup(pcfg, name); if (setting == NULL) { return true; @@ -133,14 +134,15 @@ bool must_use parse_cfg_condlst(struct list_node *list, const config_t *pcfg, co if (config_setting_is_array(setting)) { int i = config_setting_length(setting); while (i--) { - if (!c2_parse(list, config_setting_get_string_elem(setting, i), NULL)) { + if (!c2_parse(list, config_setting_get_string_elem(setting, i), + NULL, deprecated)) { return false; } } } // Treat it as a single pattern if it's a string else if (CONFIG_TYPE_STRING == config_setting_type(setting)) { - if (!c2_parse(list, config_setting_get_string(setting), NULL)) { + if (!c2_parse(list, config_setting_get_string(setting), NULL, deprecated)) { return false; } } @@ -153,7 +155,7 @@ bool must_use parse_cfg_condlst(struct list_node *list, const config_t *pcfg, co static inline bool parse_cfg_condlst_with_prefix(struct list_node *list, const config_t *pcfg, const char *name, void *(*parse_prefix)(const char *, const char **, void *), - void (*free_value)(void *), void *user_data) { + void (*free_value)(void *), void *user_data, bool *deprecated) { config_setting_t *setting = config_lookup(pcfg, name); if (setting == NULL) { return true; @@ -164,7 +166,7 @@ parse_cfg_condlst_with_prefix(struct list_node *list, const config_t *pcfg, cons while (i--) { if (!c2_parse_with_prefix( list, config_setting_get_string_elem(setting, i), - parse_prefix, free_value, user_data)) { + parse_prefix, free_value, user_data, deprecated)) { return false; } } @@ -172,7 +174,7 @@ parse_cfg_condlst_with_prefix(struct list_node *list, const config_t *pcfg, cons // Treat it as a single pattern if it's a string else if (config_setting_type(setting) == CONFIG_TYPE_STRING) { if (!c2_parse_with_prefix(list, config_setting_get_string(setting), - parse_prefix, free_value, user_data)) { + parse_prefix, free_value, user_data, deprecated)) { return false; } } @@ -575,8 +577,8 @@ static const struct { {"transparent-clipping", offsetof(struct window_maybe_options, transparent_clipping)}, }; -static c2_condition * -parse_rule(struct list_node *rules, config_setting_t *setting, struct script ***out_scripts) { +static c2_condition *parse_rule(struct list_node *rules, config_setting_t *setting, + struct script ***out_scripts, bool *deprecated) { if (!config_setting_is_group(setting)) { log_error("Invalid rule at line %d. It must be a group.", config_setting_source_line(setting)); @@ -587,7 +589,7 @@ parse_rule(struct list_node *rules, config_setting_t *setting, struct script *** const char *sval; c2_condition *rule = NULL; if (config_setting_lookup_string(setting, "match", &sval)) { - rule = c2_parse(rules, sval, NULL); + rule = c2_parse(rules, sval, NULL, deprecated); if (!rule) { log_error("Failed to parse rule at line %d.", config_setting_source_line(setting)); @@ -633,7 +635,7 @@ parse_rule(struct list_node *rules, config_setting_t *setting, struct script *** } static void parse_rules(struct list_node *rules, config_setting_t *setting, - struct script ***out_scripts) { + struct script ***out_scripts, bool *deprecated) { if (!config_setting_is_list(setting)) { log_error("Invalid value for \"rules\" at line %d. It must be a list.", config_setting_source_line(setting)); @@ -642,7 +644,7 @@ static void parse_rules(struct list_node *rules, config_setting_t *setting, const auto length = (unsigned int)config_setting_length(setting); for (unsigned int i = 0; i < length; i++) { auto sub = config_setting_get_elem(setting, i); - parse_rule(rules, sub, out_scripts); + parse_rule(rules, sub, out_scripts, deprecated); } } @@ -667,10 +669,6 @@ resolve_include(config_t *cfg, const char *include_dir, const char *path, const } bool parse_config_libconfig(options_t *opt, const char *config_file) { /*NOLINT(readability-function-cognitive-complexity)*/ - const char *deprecation_message = - "option has been deprecated. Please remove it from your configuration file. " - "If you encounter any problems without this feature, please feel free to " - "open a bug report"; char *path = NULL; FILE *f; config_t cfg; @@ -728,6 +726,7 @@ bool parse_config_libconfig(options_t *opt, const char *config_file) { /*NOLINT( opt->log_level = string_to_log_level(sval); if (opt->log_level == LOG_LEVEL_INVALID) { log_warn("Invalid log level, defaults to WARN"); + record_problematic_option(opt, "log-level"); } else { log_set_level_tls(opt->log_level); } @@ -769,7 +768,11 @@ bool parse_config_libconfig(options_t *opt, const char *config_file) { /*NOLINT( config_setting_t *rules = config_lookup(&cfg, "rules"); if (rules) { - parse_rules(&opt->rules, rules, &opt->all_scripts); + bool deprecated = false; + parse_rules(&opt->rules, rules, &opt->all_scripts, &deprecated); + if (deprecated) { + report_deprecated_option(opt, "rules", false); + } c2_condition_list_foreach(&opt->rules, i) { auto data = (struct window_maybe_options *)c2_condition_get_data(i); if (data->shader == NULL) { @@ -807,16 +810,14 @@ bool parse_config_libconfig(options_t *opt, const char *config_file) { /*NOLINT( if (config_lookup_float(&cfg, "inactive-opacity", &dval)) { opt->inactive_opacity = normalize_d(dval); if (!list_is_empty(&opt->rules)) { - log_warn_both_style_of_rules("inactive-opacity"); - opt->has_both_style_of_rules = true; + log_warn_both_style_of_rules(opt, "inactive-opacity"); } } // --active_opacity if (config_lookup_float(&cfg, "active-opacity", &dval)) { opt->active_opacity = normalize_d(dval); if (!list_is_empty(&opt->rules)) { - log_warn_both_style_of_rules("active-opacity"); - opt->has_both_style_of_rules = true; + log_warn_both_style_of_rules(opt, "active-opacity"); } } // --corner-radius @@ -861,32 +862,27 @@ bool parse_config_libconfig(options_t *opt, const char *config_file) { /*NOLINT( // --inactive-opacity-override if (lcfg_lookup_bool(&cfg, "inactive-opacity-override", &opt->inactive_opacity_override) && !list_is_empty(&opt->rules)) { - log_warn_both_style_of_rules("inactive-opacity-override"); - opt->has_both_style_of_rules = true; + log_warn_both_style_of_rules(opt, "inactive-opacity-override"); } // --inactive-dim if (config_lookup_float(&cfg, "inactive-dim", &opt->inactive_dim) && !list_is_empty(&opt->rules)) { - log_warn_both_style_of_rules("inactive-dim"); - opt->has_both_style_of_rules = true; + log_warn_both_style_of_rules(opt, "inactive-dim"); } // --mark-wmwin-focused if (lcfg_lookup_bool(&cfg, "mark-wmwin-focused", &opt->mark_wmwin_focused) && !list_is_empty(&opt->rules)) { - log_warn_both_style_of_rules("mark-wmwin-focused"); - opt->has_both_style_of_rules = true; + log_warn_both_style_of_rules(opt, "mark-wmwin-focused"); } // --mark-ovredir-focused if (lcfg_lookup_bool(&cfg, "mark-ovredir-focused", &opt->mark_ovredir_focused) && !list_is_empty(&opt->rules)) { - log_warn_both_style_of_rules("mark-ovredir-focused"); - opt->has_both_style_of_rules = true; + log_warn_both_style_of_rules(opt, "mark-ovredir-focused"); } // --shadow-ignore-shaped if (lcfg_lookup_bool(&cfg, "shadow-ignore-shaped", &opt->shadow_ignore_shaped) && !list_is_empty(&opt->rules)) { - log_warn_both_style_of_rules("shadow-ignore-shaped"); - opt->has_both_style_of_rules = true; + log_warn_both_style_of_rules(opt, "shadow-ignore-shaped"); } // --detect-rounded-corners lcfg_lookup_bool(&cfg, "detect-rounded-corners", &opt->detect_rounded_corners); @@ -894,13 +890,14 @@ bool parse_config_libconfig(options_t *opt, const char *config_file) { /*NOLINT( if (lcfg_lookup_bool(&cfg, "xinerama-shadow-crop", &opt->crop_shadow_to_monitor)) { log_warn("xinerama-shadow-crop is deprecated. Use crop-shadow-to-monitor " "instead."); + record_problematic_option(opt, "xinerama-shadow-crop"); } lcfg_lookup_bool(&cfg, "crop-shadow-to-monitor", &opt->crop_shadow_to_monitor); // --detect-client-opacity lcfg_lookup_bool(&cfg, "detect-client-opacity", &opt->detect_client_opacity); // --refresh-rate if (config_lookup_int(&cfg, "refresh-rate", &ival)) { - log_warn("The refresh-rate %s", deprecation_message); + report_deprecated_option(opt, "refresh-rate", false); } // --vsync if (config_lookup_string(&cfg, "vsync", &sval)) { @@ -924,6 +921,7 @@ bool parse_config_libconfig(options_t *opt, const char *config_file) { /*NOLINT( if (*sval != '/') { log_warn("The log-file in your configuration file is not an " "absolute path"); + record_problematic_option(opt, "log-file"); } opt->logpath = strdup(sval); } @@ -935,6 +933,7 @@ bool parse_config_libconfig(options_t *opt, const char *config_file) { /*NOLINT( if (config_lookup_int(&cfg, "unredir-if-possible-delay", &ival)) { if (ival < 0) { log_warn("Invalid unredir-if-possible-delay %d", ival); + record_problematic_option(opt, "unredir-if-possible-delay"); } else { opt->unredir_if_possible_delay = ival; } @@ -951,52 +950,60 @@ bool parse_config_libconfig(options_t *opt, const char *config_file) { /*NOLINT( lcfg_lookup_bool(&cfg, "transparent-clipping", &opt->transparent_clipping); // --dithered_present lcfg_lookup_bool(&cfg, "dithered-present", &opt->dithered_present); + const struct { + const char *name; + ptrdiff_t offset; + void *(*parse_prefix)(const char *, const char **, void *); + void (*free_value)(void *); + void *user_data; + } rule_list[] = { + {"transparent-clipping-exclude", + offsetof(struct options, transparent_clipping_blacklist)}, + {"shadow-exclude", offsetof(struct options, shadow_blacklist)}, + {"clip-shadow-above", offsetof(struct options, shadow_clip_list)}, + {"fade-exclude", offsetof(struct options, fade_blacklist)}, + {"focus-exclude", offsetof(struct options, focus_blacklist)}, + {"invert-color-include", offsetof(struct options, invert_color_list)}, + {"blur-background-exclude", offsetof(struct options, blur_background_blacklist)}, + {"unredir-if-possible-exclude", + offsetof(struct options, unredir_if_possible_blacklist)}, + {"rounded-corners-exclude", offsetof(struct options, rounded_corners_blacklist)}, + {"corner-radius-rules", offsetof(struct options, corner_radius_rules), + parse_numeric_prefix, NULL, (int[]){0, INT_MAX}}, + {"opacity-rule", offsetof(struct options, opacity_rules), + parse_numeric_prefix, NULL, (int[]){0, 100}}, + {"window-shader-fg-rule", offsetof(struct options, window_shader_fg_rules), + parse_window_shader_prefix, free, (void *)config_get_include_dir(&cfg)}, + }; if (!list_is_empty(&opt->rules)) { - static const char *rule_list[] = { - "transparent-clipping-exclude", - "shadow-exclude", - "clip-shadow-above", - "fade-exclude", - "focus-exclude", - "invert-color-include", - "blur-background-exclude", - "unredir-if-possible-exclude", - "rounded-corners-exclude", - "corner-radius-rules", - "opacity-rule", - "window-shader-fg-rule", - "wintypes", - }; - for (size_t i = 0; i < sizeof(rule_list) / sizeof(rule_list[0]); i++) { - if (config_lookup(&cfg, rule_list[i])) { - log_warn_both_style_of_rules(rule_list[i]); - opt->has_both_style_of_rules = true; + for (size_t i = 0; i < ARR_SIZE(rule_list); i++) { + if (config_lookup(&cfg, rule_list[i].name)) { + log_warn_both_style_of_rules(opt, rule_list[i].name); + } + } + if (config_lookup(&cfg, "wintypes")) { + log_warn_both_style_of_rules(opt, "wintypes"); + } + } else { + for (size_t i = 0; i < ARR_SIZE(rule_list); i++) { + bool deprecated = false; + void *dst = (char *)opt + rule_list[i].offset; + if ((rule_list[i].parse_prefix != NULL && + !parse_cfg_condlst_with_prefix( + dst, &cfg, rule_list[i].name, rule_list[i].parse_prefix, + rule_list[i].free_value, rule_list[i].user_data, &deprecated)) || + (rule_list[i].parse_prefix == NULL && + !parse_cfg_condlst(dst, &cfg, rule_list[i].name, &deprecated))) { + goto out; + } + if (deprecated) { + log_warn("Rule option \"%s\" contains deprecated syntax " + "(see above).", + rule_list[i].name); + record_problematic_option(opt, rule_list[i].name); } } - } else if (!parse_cfg_condlst(&opt->transparent_clipping_blacklist, &cfg, - "transparent-clipping-exclude") || - !parse_cfg_condlst(&opt->shadow_blacklist, &cfg, "shadow-exclude") || - !parse_cfg_condlst(&opt->shadow_clip_list, &cfg, "clip-shadow-above") || - !parse_cfg_condlst(&opt->fade_blacklist, &cfg, "fade-exclude") || - !parse_cfg_condlst(&opt->focus_blacklist, &cfg, "focus-exclude") || - !parse_cfg_condlst(&opt->invert_color_list, &cfg, "invert-color-include") || - !parse_cfg_condlst(&opt->blur_background_blacklist, &cfg, - "blur-background-exclude") || - !parse_cfg_condlst(&opt->unredir_if_possible_blacklist, &cfg, - "unredir-if-possible-exclude") || - !parse_cfg_condlst(&opt->rounded_corners_blacklist, &cfg, - "rounded-corners-exclude") || - !parse_cfg_condlst_with_prefix( - &opt->corner_radius_rules, &cfg, "corner-radius-rules", - parse_numeric_prefix, NULL, (int[]){0, INT_MAX}) || - !parse_cfg_condlst_with_prefix(&opt->opacity_rules, &cfg, "opacity-rule", - parse_numeric_prefix, NULL, (int[]){0, 100}) || - !parse_cfg_condlst_with_prefix(&opt->window_shader_fg_rules, &cfg, - "window-shader-fg-rule", - parse_window_shader_prefix, free, - (void *)config_get_include_dir(&cfg))) { - goto out; } // --blur-method @@ -1034,16 +1041,15 @@ bool parse_config_libconfig(options_t *opt, const char *config_file) { /*NOLINT( } // --resize-damage if (config_lookup_int(&cfg, "resize-damage", &opt->resize_damage)) { - log_warn("resize-damage is deprecated. Please remove it from your " - "configuration file."); + report_deprecated_option(opt, "resize-damage", false); } // --glx-no-stencil if (lcfg_lookup_bool(&cfg, "glx-no-stencil", &opt->glx_no_stencil)) { - log_warn("glx-no-stencil %s", deprecation_message); + report_deprecated_option(opt, "glx-no-stencil", false); } // --glx-no-rebind-pixmap if (lcfg_lookup_bool(&cfg, "glx-no-rebind-pixmap", &opt->glx_no_rebind_pixmap)) { - log_warn("glx-no-rebind-pixmap %s", deprecation_message); + report_deprecated_option(opt, "glx-no-rebind-pixmap", false); } lcfg_lookup_bool(&cfg, "force-win-blend", &opt->force_win_blend); // --use-damage @@ -1054,6 +1060,7 @@ bool parse_config_libconfig(options_t *opt, const char *config_file) { /*NOLINT( opt->use_damage && opt->max_brightness < 1) { log_warn("max-brightness requires use-damage = false. Falling back to " "1.0"); + record_problematic_option(opt, "max-brightness"); opt->max_brightness = 1.0; } @@ -1072,6 +1079,7 @@ bool parse_config_libconfig(options_t *opt, const char *config_file) { /*NOLINT( int method = parse_blur_method(sval); if (method >= BLUR_METHOD_INVALID) { log_warn("Invalid blur method %s, ignoring.", sval); + record_problematic_option(opt, "blur method"); } else { opt->blur_method = (enum blur_method)method; } @@ -1083,6 +1091,7 @@ bool parse_config_libconfig(options_t *opt, const char *config_file) { /*NOLINT( opt->blur_kerns = parse_blur_kern_lst(sval, &opt->blur_kernel_count); if (!opt->blur_kerns) { log_warn("Failed to parse blur kernel: %s", sval); + record_problematic_option(opt, "blur kernel"); } } @@ -1095,6 +1104,7 @@ bool parse_config_libconfig(options_t *opt, const char *config_file) { /*NOLINT( if (*sval != '/') { log_warn("The write-pid-path in your configuration file is not" " an absolute path"); + record_problematic_option(opt, "write-pid-path"); } opt->write_pid_path = strdup(sval); } diff --git a/src/log.h b/src/log.h index 3e20f61129..9f78dc594b 100644 --- a/src/log.h +++ b/src/log.h @@ -33,12 +33,13 @@ enum log_level { } \ } while (0) -#define LOG(level, x, ...) \ - do { \ - if (LOG_LEVEL_##level >= log_get_level_tls()) { \ - log_printf(tls_logger, LOG_LEVEL_##level, __func__, x, ##__VA_ARGS__); \ - } \ +#define LOG_(level, x, ...) \ + do { \ + if (level >= log_get_level_tls()) { \ + log_printf(tls_logger, level, __func__, x, ##__VA_ARGS__); \ + } \ } while (0) +#define LOG(level, x, ...) LOG_(LOG_LEVEL_##level, x, ##__VA_ARGS__) #define log_trace(x, ...) LOG_UNLIKELY(TRACE, x, ##__VA_ARGS__) #define log_verbose(x, ...) LOG_UNLIKELY(VERBOSE, x, ##__VA_ARGS__) #define log_debug(x, ...) LOG_UNLIKELY(DEBUG, x, ##__VA_ARGS__) diff --git a/src/options.c b/src/options.c index 0ca46bd885..cdeec69946 100644 --- a/src/options.c +++ b/src/options.c @@ -71,8 +71,7 @@ static bool set_rule_flag(const struct picom_option *arg_opt, const struct picom const char * /*arg_str*/, void *output) { auto opt = (struct options *)output; if (!list_is_empty(&opt->rules)) { - log_warn_both_style_of_rules(arg_opt->long_name); - opt->has_both_style_of_rules = true; + log_warn_both_style_of_rules(opt, arg_opt->long_name); return true; } *(bool *)(output + arg->offset) = true; @@ -115,8 +114,7 @@ static bool store_rule_float(const struct picom_option *arg_opt, const struct pi const char *arg_str, void *output) { auto opt = (struct options *)output; if (!list_is_empty(&opt->rules)) { - log_warn_both_style_of_rules(arg_opt->long_name); - opt->has_both_style_of_rules = true; + log_warn_both_style_of_rules(opt, arg_opt->long_name); return true; } return store_float(arg_opt, arg, arg_str, output); @@ -157,16 +155,24 @@ static bool store_rules(const struct picom_option *arg_opt, const struct picom_a const struct picom_rules_parser *parser = arg->user_data; struct options *opt = (struct options *)output; if (!list_is_empty(&opt->rules)) { - log_warn_both_style_of_rules(arg_opt->long_name); - opt->has_both_style_of_rules = true; + log_warn_both_style_of_rules(opt, arg_opt->long_name); return true; } auto rules = (struct list_node *)(output + arg->offset); + bool deprecated = false, succeeded; if (!parser->parse_prefix) { - return c2_parse(rules, arg_str, NULL) != NULL; - } - return c2_parse_with_prefix(rules, arg_str, parser->parse_prefix, - parser->free_value, parser->user_data); + succeeded = c2_parse(rules, arg_str, NULL, &deprecated) != NULL; + } else { + succeeded = c2_parse_with_prefix(rules, arg_str, parser->parse_prefix, + parser->free_value, parser->user_data, + &deprecated); + } + if (deprecated) { + log_warn("Rule option --%s contains deprecated syntax (see above).", + arg_opt->long_name); + record_problematic_option(opt, arg_opt->long_name); + } + return succeeded; } static bool store_fixed_enum(const struct picom_option * /*opt*/, const struct picom_arg *arg, @@ -189,10 +195,7 @@ static bool reject(const struct picom_option * /*opt*/, const struct picom_arg * static bool say_deprecated(const struct picom_option *opt, const struct picom_arg *arg, const char *arg_str, void *output) { const struct picom_deprecated_arg *deprecation = arg->user_data; - enum log_level level = deprecation->error ? LOG_LEVEL_ERROR : LOG_LEVEL_WARN; - log_printf(tls_logger, level, __func__, - "Option `--%s` has been deprecated. Please remove it. %s", - opt->long_name, deprecation->message); + report_deprecated_option(output, opt->long_name, deprecation->error); return deprecation->inner.handler(opt, &deprecation->inner, arg_str, output); } diff --git a/src/picom.c b/src/picom.c index 649a395879..38f2ba09f8 100644 --- a/src/picom.c +++ b/src/picom.c @@ -63,6 +63,7 @@ #include "utils/process.h" #include "utils/statistics.h" #include "utils/str.h" +#include "utils/ui.h" #include "utils/uthash_extra.h" #include "vblank.h" #include "wm/defs.h" @@ -1891,6 +1892,72 @@ static struct window_options win_options_from_config(const struct options *opts) return ret; } +static void show_config_warning_message_box(struct options *opt) { + if (opt->problematic_options == NULL) { + return; + } + + struct x_connection c; + if (spawn_picomling(&c) != 0) { + return; + } + + struct ui *ui = ui_new(&c); + if (ui == NULL) { + exit(1); + } + + auto lines = HASH_COUNT(opt->problematic_options) + 4; + struct ui_message_box_line normal_line_template = { + .text = NULL, + .color = UI_COLOR_WHITE, + .style = UI_STYLE_NORMAL, + .justify = UI_JUSTIFY_LEFT, + .pad_bottom = 3, + }; + struct ui_message_box_content *content = + malloc(sizeof(*content) + lines * sizeof(struct ui_message_box_line)); + content->num_lines = lines; + content->margin = 10; + content->scale = 0; + content->lines[0] = (struct ui_message_box_line){ + .text = "picom Warning!", + .color = UI_COLOR_YELLOW, + .style = UI_STYLE_BOLD, + .justify = UI_JUSTIFY_CENTER, + .pad_bottom = 15, + }; + content->lines[1] = normal_line_template; + content->lines[1].text = + "Some of your settings have generated warnings. Check the console"; + content->lines[2] = normal_line_template; + content->lines[2].text = + "output of picom for more information. Offending options are:"; + content->lines[2].pad_bottom = 10; + struct option_name *o, *no; + unsigned pos = 3; + HASH_ITER(hh, opt->problematic_options, o, no) { + char *buf = NULL; + casprintf(&buf, " * %s", o->name); + content->lines[pos] = normal_line_template; + content->lines[pos].text = buf; + pos++; + } + content->lines[pos - 1].pad_bottom = 10; + content->lines[pos] = normal_line_template; + content->lines[pos].text = "(Press ESC to close)"; + content->lines[pos].pad_bottom = 10; + content->lines[pos].justify = UI_JUSTIFY_CENTER; + ui_message_box_content_plan(ui, &c, content); + ui_message_box_show(ui, &c, content, 15); + + for (unsigned i = 3; i < lines - 1; i++) { + free((void *)content->lines[i].text); + } + free(content); + exit(0); +} + // NOLINTBEGIN(readability-function-cognitive-complexity) /// Initialize a session. @@ -2060,6 +2127,8 @@ static session_t *session_init(int argc, char **argv, Display *dpy, return NULL; } + show_config_warning_message_box(&ps->o); + const char *basename = strrchr(argv[0], '/') ? strrchr(argv[0], '/') + 1 : argv[0]; if (strcmp(basename, "picom-inspect") == 0) {