// SPDX-License-Identifier: MPL-2.0 // Copyright (c) Yuxuan Shui #include #include #include #include #include #include #include #include #include #include // for xcb_render_fixed_t, XXX #include "backend/backend.h" #include "common.h" #include "config.h" #include "log.h" #include "options.h" #include "string_utils.h" #include "utils.h" #include "win.h" #include "x.h" #pragma GCC diagnostic error "-Wunused-parameter" struct picom_option; struct picom_arg { const char *name; ptrdiff_t offset; const void *user_data; bool (*handler)(const struct picom_option *, const struct picom_arg *, const char *optarg, void *output); }; struct picom_arg_parser { int (*parse)(const char *); int invalid_value; }; struct picom_rules_parser { void *(*parse_prefix)(const char *, const char **end, void *data); void (*free_value)(void *); void *user_data; }; struct picom_deprecated_arg { const char *message; struct picom_arg inner; bool error; }; struct picom_option { const char *long_name; int has_arg; struct picom_arg arg; const char *help; }; static bool set_flag(const struct picom_option * /*opt*/, const struct picom_arg *arg, const char * /*arg_str*/, void *output) { *(bool *)(output + arg->offset) = true; return true; } static bool unset_flag(const struct picom_option * /*opt*/, const struct picom_arg *arg, const char * /*arg_str*/, void *output) { *(bool *)(output + arg->offset) = false; return true; } static bool parse_with(const struct picom_option *opt, const struct picom_arg *arg, const char *arg_str, void *output) { const struct picom_arg_parser *parser = arg->user_data; int *dst = (int *)(output + arg->offset); *dst = parser->parse(arg_str); if (*dst == parser->invalid_value) { log_error("Invalid argument for option `--%s`: %s", opt->long_name, arg_str); return false; } return true; } static bool store_float(const struct picom_option *opt, const struct picom_arg *arg, const char *arg_str, void *output) { double *dst = (double *)(output + arg->offset); const double *minmax = (const double *)arg->user_data; const char *endptr = NULL; *dst = strtod_simple(arg_str, &endptr); if (!endptr || *endptr != '\0') { log_error("Argument for option `--%s` is not a valid float number: %s", opt->long_name, arg_str); return false; } *dst = max2(minmax[0], min2(*dst, minmax[1])); return true; } static bool store_int(const struct picom_option *opt, const struct picom_arg *arg, const char *arg_str, void *output) { const int *minmax = (const int *)arg->user_data; int *dst = (int *)(output + arg->offset); if (!parse_int(arg_str, dst)) { log_error("Argument for option `--%s` is not a valid integer: %s", opt->long_name, arg_str); return false; } *dst = max2(minmax[0], min2(*dst, minmax[1])); return true; } static bool store_string(const struct picom_option * /*opt*/, const struct picom_arg *arg, const char *arg_str, void *output) { char **dst = (char **)(output + arg->offset); free(*dst); *dst = strdup(arg_str); return true; } static bool store_rules(const struct picom_option * /*opt*/, const struct picom_arg *arg, const char *arg_str, void *output) { const struct picom_rules_parser *parser = arg->user_data; auto rules = (c2_lptr_t **)(output + arg->offset); 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); } static bool store_fixed_enum(const struct picom_option * /*opt*/, const struct picom_arg *arg, const char * /*arg_str*/, void *output) { const int *value = (const int *)arg->user_data; *(int *)(output + arg->offset) = *value; return true; } static bool noop(const struct picom_option * /*opt*/, const struct picom_arg * /*arg*/, const char * /*arg_str*/, void * /*output*/) { return true; } static bool reject(const struct picom_option * /*opt*/, const struct picom_arg * /*arg*/, const char * /*arg_str*/, void * /*output*/) { return false; } 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); return deprecation->inner.handler(opt, &deprecation->inner, arg_str, output); } #define OFFSET(member) offsetof(struct options, member) #define ENABLE(member) \ no_argument, { \ .offset = OFFSET(member), .handler = set_flag, \ } #define DISABLE(member) \ no_argument, { \ .offset = OFFSET(member), .handler = unset_flag, \ } #define IGNORE(has_arg) \ has_arg, { \ .handler = noop, \ } #define REJECT(has_arg) \ has_arg, { \ .handler = reject, \ } #define DO(fn) \ required_argument, { \ .handler = (fn), \ } #define PARSE_WITH(fn, invalid, member) \ required_argument, { \ .offset = OFFSET(member), .handler = parse_with, \ .user_data = (struct picom_arg_parser[]){{ \ .invalid_value = (invalid), \ .parse = (fn), \ }}, \ } #define FLOAT(member, min, max) \ required_argument, { \ .offset = OFFSET(member), .handler = store_float, \ .user_data = (double[]){min, max}, \ } #define INTEGER(member, min, max) \ required_argument, { \ .offset = OFFSET(member), .handler = store_int, \ .user_data = (int[]){min, max}, \ } #define NAMED_STRING(member, name_) \ required_argument, { \ .offset = OFFSET(member), .handler = store_string, .name = (name_) \ } #define STRING(member) NAMED_STRING(member, NULL) #define NAMED_RULES(member, name_, ...) \ required_argument, { \ .offset = OFFSET(member), .handler = store_rules, .name = (name_), \ .user_data = (struct picom_rules_parser[]) { \ __VA_ARGS__ \ } \ } #define NUMERIC_RULES(member, value, min, max) \ NAMED_RULES(member, value ":COND", \ {.parse_prefix = parse_numeric_prefix, .user_data = (int[]){min, max}}) #define RULES(member) NAMED_RULES(member, "COND", {}) #define FIXED(member, value) \ no_argument, { \ .offset = OFFSET(member), .handler = store_fixed_enum, \ .user_data = (int[]){value}, \ } #define SAY_DEPRECATED_(error_, msg, has_arg, ...) \ has_arg, { \ .handler = say_deprecated, .user_data = (struct picom_deprecated_arg[]) { \ {.message = (msg), .inner = __VA_ARGS__, .error = error_}, \ } \ } #define SAY_DEPRECATED(error_, msg, ...) SAY_DEPRECATED_(error_, msg, __VA_ARGS__) #define WARN_DEPRECATED(...) \ SAY_DEPRECATED_(false, \ "If you encounter problems without this feature, please " \ "feel free to open a bug report.", \ __VA_ARGS__) #define WARN_DEPRECATED_ENABLED(...) \ SAY_DEPRECATED_(false, "Its functionality will always be enabled. ", __VA_ARGS__) #define ERROR_DEPRECATED(has_arg) SAY_DEPRECATED(true, "", REJECT(has_arg)) static bool store_shadow_color(const struct picom_option * /*opt*/, const struct picom_arg * /*arg*/, const char *arg_str, void *output) { struct options *opt = (struct options *)output; struct color rgb; rgb = hex_to_rgb(arg_str); opt->shadow_red = rgb.red; opt->shadow_green = rgb.green; opt->shadow_blue = rgb.blue; return true; } static bool handle_menu_opacity(const struct picom_option * /*opt*/, const struct picom_arg * /*arg*/, const char *arg_str, void *output) { struct options *opt = (struct options *)output; const char *endptr = NULL; double tmp = max2(0.0, min2(1.0, strtod_simple(arg_str, &endptr))); if (!endptr || *endptr != '\0') { return false; } opt->wintype_option_mask[WINTYPE_DROPDOWN_MENU].opacity = true; opt->wintype_option_mask[WINTYPE_POPUP_MENU].opacity = true; opt->wintype_option[WINTYPE_POPUP_MENU].opacity = tmp; opt->wintype_option[WINTYPE_DROPDOWN_MENU].opacity = tmp; return true; } static bool store_blur_kern(const struct picom_option * /*opt*/, const struct picom_arg * /*arg*/, const char *arg_str, void *output) { struct options *opt = (struct options *)output; opt->blur_kerns = parse_blur_kern_lst(arg_str, &opt->blur_kernel_count); return opt->blur_kerns != NULL; } static bool store_benchmark_wid(const struct picom_option * /*opt*/, const struct picom_arg * /*arg*/, const char *arg_str, void *output) { struct options *opt = (struct options *)output; const char *endptr = NULL; opt->benchmark_wid = (xcb_window_t)strtoul(arg_str, (char **)&endptr, 0); if (!endptr || *endptr != '\0') { log_error("Invalid window ID for `--benchmark-wid`: %s", arg_str); return false; } return true; } #define WINDOW_SHADER_RULE \ { .parse_prefix = parse_window_shader_prefix_with_cwd, .free_value = free, } #ifdef CONFIG_OPENGL #define BACKENDS "xrender, glx" #else #define BACKENDS "xrender" #endif // clang-format off static const struct option *longopts = NULL; static const struct picom_option picom_options[] = { // As you can see, aligning this table is difficult... // Rejected options, we shouldn't be able to reach `get_cfg` when these are set ['h'] = {"help" , REJECT(no_argument), "Print this help message and exit."}, [318] = {"version", REJECT(no_argument), "Print version number and exit."}, // Ignored options, these are already handled by `get_early_cfg` [314] = {"show-all-xerrors", IGNORE(no_argument)}, ['b'] = {"daemon" , IGNORE(no_argument) , "Daemonize process."}, [256] = {"config" , IGNORE(required_argument), "Path to the configuration file."}, // Simple flags ['c'] = {"shadow" , ENABLE(shadow_enable) , "Enabled client-side shadows on windows."}, ['f'] = {"fading" , ENABLE(fading_enable) , "Fade windows in/out when opening/closing and when opacity changes, " "unless --no-fading-openclose is used."}, [262] = {"mark-wmwin-focused" , ENABLE(mark_wmwin_focused) , "Try to detect WM windows and mark them as active."}, [264] = {"mark-ovredir-focused" , ENABLE(mark_ovredir_focused) , "Mark windows that have no WM frame as active."}, [265] = {"no-fading-openclose" , ENABLE(no_fading_openclose) , "Do not fade on window open/close."}, [266] = {"shadow-ignore-shaped" , ENABLE(shadow_ignore_shaped) , "Do not paint shadows on shaped windows. (Deprecated, use --shadow-exclude " "\'bounding_shaped\' or --shadow-exclude \'bounding_shaped && " "!rounded_corners\' instead.)"}, [268] = {"detect-client-opacity" , ENABLE(detect_client_opacity) , "Detect _NET_WM_WINDOW_OPACITY on client windows, useful for window " "managers not passing _NET_WM_WINDOW_OPACITY of client windows to frame"}, [270] = {"vsync" , ENABLE(vsync) , "Enable VSync"}, [271] = {"crop-shadow-to-monitor" , ENABLE(crop_shadow_to_monitor) , "Crop shadow of a window fully on a particular monitor to that monitor. " "This is currently implemented using the X RandR extension"}, [276] = {"use-ewmh-active-win" , ENABLE(use_ewmh_active_win) , "Use _NET_WM_ACTIVE_WINDOW on the root window to determine which window is " "focused instead of using FocusIn/Out events"}, [278] = {"unredir-if-possible" , ENABLE(unredir_if_possible) , "Unredirect all windows if a full-screen opaque window is detected, to " "maximize performance for full-screen applications."}, [280] = {"inactive-dim-fixed" , ENABLE(inactive_dim_fixed) , "Use fixed inactive dim value."}, [281] = {"detect-transient" , ENABLE(detect_transient) , "Use WM_TRANSIENT_FOR to group windows, and consider windows in the same " "group focused at the same time."}, [282] = {"detect-client-leader" , ENABLE(detect_client_leader) , "Use WM_CLIENT_LEADER to group windows, and consider windows in the same group " "focused at the same time. This usually means windows from the same application " "will be considered focused or unfocused at the same time. WM_TRANSIENT_FOR has " "higher priority if --detect-transient is enabled, too."}, [284] = {"blur-background-frame" , ENABLE(blur_background_frame) , "Blur background of windows when the window frame is not opaque. Implies " "--blur-background."}, [285] = {"blur-background-fixed" , ENABLE(blur_background_fixed) , "Use fixed blur strength instead of adjusting according to window opacity."}, #ifdef CONFIG_DBUS [286] = {"dbus" , ENABLE(dbus) , "Enable remote control via D-Bus. See the D-BUS API section in the man page " "for more details."}, #endif [311] = {"vsync-use-glfinish" , ENABLE(vsync_use_glfinish)}, [313] = {"xrender-sync-fence" , ENABLE(xrender_sync_fence) , "Additionally use X Sync fence to sync clients' draw calls. Needed on " "nvidia-drivers with GLX backend for some users."}, [315] = {"no-fading-destroyed-argb" , ENABLE(no_fading_destroyed_argb) , "Do not fade destroyed ARGB windows with WM frame. Workaround bugs in Openbox, " "Fluxbox, etc."}, [316] = {"force-win-blend" , ENABLE(force_win_blend) , "Force all windows to be painted with blending. Useful if you have a custom " "shader that could turn opaque pixels transparent."}, [319] = {"no-x-selection" , ENABLE(no_x_selection)}, [323] = {"use-damage" , ENABLE(use_damage) , "Render only the damaged (changed) part of the screen"}, [324] = {"no-use-damage" , DISABLE(use_damage) , "Disable the use of damage information. This cause the whole screen to be" "redrawn every time, instead of the part of the screen that has actually " "changed. Potentially degrades the performance, but might fix some artifacts."}, [260] = {"inactive-opacity-override", ENABLE(inactive_opacity_override), "Inactive opacity set by -i overrides value of _NET_WM_WINDOW_OPACITY."}, [267] = {"detect-rounded-corners" , ENABLE(detect_rounded_corners) , "Try to detect windows with rounded corners and don't consider them shaped " "windows. Affects --shadow-ignore-shaped, --unredir-if-possible, and " "possibly others. You need to turn this on manually if you want to match " "against rounded_corners in conditions."}, [298] = {"glx-no-rebind-pixmap" , ENABLE(glx_no_rebind_pixmap)}, [291] = {"glx-no-stencil" , ENABLE(glx_no_stencil)}, [325] = {"no-vsync" , DISABLE(vsync) , "Disable VSync"}, [327] = {"transparent-clipping" , ENABLE(transparent_clipping) , "Make transparent windows clip other windows like non-transparent windows do, " "instead of blending on top of them"}, [339] = {"dithered-present" , ENABLE(dithered_present) , "Use higher precision during rendering, and apply dither when presenting the " "rendered screen. Reduces banding artifacts, but might cause performance " "degradation. Only works with OpenGL."}, [341] = {"no-frame-pacing" , DISABLE(frame_pacing) , "Disable frame pacing. This might increase the latency."}, [733] = {"legacy-backends" , ENABLE(legacy_backends) , "Use deprecated version of the backends."}, [800] = {"monitor-repaint" , ENABLE(monitor_repaint) , "Highlight the updated area of the screen. For debugging."}, [801] = {"diagnostics" , ENABLE(print_diagnostics) , "Print diagnostic information"}, [802] = {"debug-mode" , ENABLE(debug_mode) , "Render into a separate window, and don't take over the screen. Useful when " "you want to attach a debugger to picom"}, [803] = {"no-ewmh-fullscreen" , ENABLE(no_ewmh_fullscreen) , "Do not use EWMH to detect fullscreen windows. Reverts to checking if a " "window is fullscreen based only on its size and coordinates."}, [804] = {"realtime" , ENABLE(use_realtime_scheduling) , "Enable realtime scheduling. This might reduce latency, but might also cause " "other issues. Disable this if you see the compositor being killed."}, // Flags that takes an argument ['r'] = {"shadow-radius" , INTEGER(shadow_radius, 0, INT_MAX) , "The blur radius for shadows. (default 12)"}, ['o'] = {"shadow-opacity" , FLOAT(shadow_opacity, 0, 1) , "The translucency for shadows. (default .75)"}, ['l'] = {"shadow-offset-x" , INTEGER(shadow_offset_x, INT_MIN, INT_MAX) , "The left offset for shadows. (default -15)"}, ['t'] = {"shadow-offset-y" , INTEGER(shadow_offset_y, INT_MIN, INT_MAX) , "The top offset for shadows. (default -15)"}, ['I'] = {"fade-in-step" , FLOAT(fade_in_step, 0, 1) , "Opacity change between steps while fading in. (default 0.028)"}, ['O'] = {"fade-out-step" , FLOAT(fade_out_step, 0, 1) , "Opacity change between steps while fading out. (default 0.03)"}, ['D'] = {"fade-delta" , INTEGER(fade_delta, 1, INT_MAX) , "The time between steps in a fade in milliseconds. (default 10)"}, ['i'] = {"inactive-opacity" , FLOAT(inactive_opacity, 0, 1) , "Opacity of inactive windows. (0.0 - 1.0)"}, ['e'] = {"frame-opacity" , FLOAT(frame_opacity, 0, 1) , "Opacity of window titlebars and borders. (0.0 - 1.0)"}, [257] = {"shadow-red" , FLOAT(shadow_red, 0, 1) , "Red color value of shadow (0.0 - 1.0, defaults to 0)."}, [258] = {"shadow-green" , FLOAT(shadow_green, 0, 1) , "Green color value of shadow (0.0 - 1.0, defaults to 0)."}, [259] = {"shadow-blue" , FLOAT(shadow_blue, 0, 1) , "Blue color value of shadow (0.0 - 1.0, defaults to 0)."}, [261] = {"inactive-dim" , FLOAT(inactive_dim, 0, 1) , "Dim inactive windows. (0.0 - 1.0, defaults to 0)"}, [283] = {"blur-background" , FIXED(blur_method, BLUR_METHOD_KERNEL) , "Blur background of semi-transparent / ARGB windows. May impact performance"}, [290] = {"backend" , PARSE_WITH(parse_backend, NUM_BKEND, backend) , "Backend. Possible values are: " BACKENDS}, [293] = {"benchmark" , INTEGER(benchmark, 0, INT_MAX) , "Benchmark mode. Repeatedly paint until reaching the specified cycles."}, [297] = {"active-opacity" , FLOAT(active_opacity, 0, 1) , "Default opacity for active windows. (0.0 - 1.0)"}, [302] = {"resize-damage" , INTEGER(resize_damage, INT_MIN, INT_MAX)}, // only used by legacy backends [309] = {"unredir-if-possible-delay" , INTEGER(unredir_if_possible_delay, 0, INT_MAX) , "Delay before unredirecting the window, in milliseconds. Defaults to 0."}, [310] = {"write-pid-path" , NAMED_STRING(write_pid_path, "PATH") , "Write process ID to a file."}, [317] = {"glx-fshader-win" , STRING(glx_fshader_win_str)}, [322] = {"log-file" , STRING(logpath) , "Path to the log file."}, [326] = {"max-brightness" , FLOAT(max_brightness, 0, 1) , "Dims windows which average brightness is above this threshold. Requires " "--no-use-damage. (default: 1.0, meaning no dimming)"}, [329] = {"blur-size" , INTEGER(blur_radius, 0, INT_MAX) , "The radius of the blur kernel for 'box' and 'gaussian' blur method."}, [330] = {"blur-deviation" , FLOAT(blur_deviation, 0, INFINITY) , "The standard deviation for the 'gaussian' blur method."}, [331] = {"blur-strength" , INTEGER(blur_strength, 0, INT_MAX) , "The strength level of the 'dual_kawase' blur method."}, [333] = {"corner-radius" , INTEGER(corner_radius, 0, INT_MAX) , "Sets the radius of rounded window corners. When > 0, the compositor will " "round the corners of windows. (defaults to 0)."}, [336] = {"window-shader-fg" , NAMED_STRING(window_shader_fg, "PATH") , "Specify GLSL fragment shader path for rendering window contents. Does not" " work when `--legacy-backends` is enabled. See man page for more details."}, [294] = {"benchmark-wid" , DO(store_benchmark_wid) , "Specify window ID to repaint in benchmark mode. If omitted or is 0, the whole" " screen is repainted."}, [301] = {"blur-kern" , DO(store_blur_kern) , "Specify the blur convolution kernel, see man page for more details"}, [332] = {"shadow-color" , DO(store_shadow_color) , "Color of shadow, as a hex RGB string (defaults to #000000)"}, // Rules [263] = {"shadow-exclude" , RULES(shadow_blacklist) , "Exclude conditions for shadows."}, [279] = {"focus-exclude" , RULES(focus_blacklist) , "Specify a list of conditions of windows that should always be considered focused."}, [288] = {"invert-color-include" , RULES(invert_color_list) , "Specify a list of conditions of windows that should be painted with " "inverted color."}, [296] = {"blur-background-exclude" , RULES(blur_background_blacklist) , "Exclude conditions for background blur."}, [300] = {"fade-exclude" , RULES(fade_blacklist) , "Exclude conditions for fading."}, [306] = {"paint-exclude" , RULES(paint_blacklist) , NULL}, [308] = {"unredir-if-possible-exclude" , RULES(unredir_if_possible_blacklist) , "Conditions of windows that shouldn't be considered full-screen for " "unredirecting screen."}, [334] = {"rounded-corners-exclude" , RULES(rounded_corners_blacklist) , "Exclude conditions for rounded corners."}, [335] = {"clip-shadow-above" , RULES(shadow_clip_list) , "Specify a list of conditions of windows to not paint a shadow over, such " "as a dock window."}, [338] = {"transparent-clipping-exclude", RULES(transparent_clipping_blacklist), "Specify a list of conditions of windows that should never have " "transparent clipping applied. Useful for screenshot tools, where you " "need to be able to see through transparent parts of the window."}, // Rules that are too long to fit in one line [304] = {"opacity-rule" , NUMERIC_RULES(opacity_rules, "OPACITY", 0, 100), "Specify a list of opacity rules, see man page for more details"}, [337] = {"window-shader-fg-rule" , NAMED_RULES(window_shader_fg_rules, "PATH", WINDOW_SHADER_RULE), "Specify GLSL fragment shader path for rendering window contents using patterns. Pattern should be " "in the format of SHADER_PATH:PATTERN, similar to --opacity-rule. SHADER_PATH can be \"default\", " "in which case the default shader will be used. Does not work when --legacy-backends is enabled. See " "man page for more details"}, [340] = {"corner-radius-rules" , NUMERIC_RULES(corner_radius_rules, "RADIUS", 0, INT_MAX), "Window rules for specific rounded corner radii."}, // Options that are too long to fit in one line [321] = {"log-level" , PARSE_WITH(string_to_log_level, LOG_LEVEL_INVALID, log_level), "Log level, possible values are: trace, debug, info, warn, error"}, [328] = {"blur-method", PARSE_WITH(parse_blur_method, BLUR_METHOD_INVALID, blur_method), "The algorithm used for background bluring. Available choices are: 'none' to disable, 'gaussian', " "'box' or 'kernel' for custom convolution blur with --blur-kern. Note: 'gaussian' and 'box' is not " "supported by --legacy-backends."}, // Deprecated options [274] = {"sw-opti" , ERROR_DEPRECATED(no_argument)}, [275] = {"vsync-aggressive" , ERROR_DEPRECATED(no_argument)}, [277] = {"respect-prop-shadow", ERROR_DEPRECATED(no_argument)}, [303] = {"glx-use-gpushader4" , ERROR_DEPRECATED(no_argument)}, [269] = {"refresh-rate" , WARN_DEPRECATED(IGNORE(required_argument))}, // Deprecated options with messages #define CLEAR_SHADOW_DEPRECATION \ "Shadows are automatically cleared now. If you want to prevent shadow from " \ "being cleared under certain types of windows, you can use the \"full-shadow\" " \ "window type option." #define MENU_OPACITY_DEPRECATION \ "Use the wintype option `opacity` of `popup_menu` and `dropdown_menu` instead." ['m'] = {"menu-opacity" , SAY_DEPRECATED(false, MENU_OPACITY_DEPRECATION , DO(handle_menu_opacity))}, ['z'] = {"clear-shadow" , SAY_DEPRECATED(false, CLEAR_SHADOW_DEPRECATION , IGNORE(no_argument))}, [272] = {"xinerama-shadow-crop", SAY_DEPRECATED(false, "Use --crop-shadow-to-monitor instead.", ENABLE(crop_shadow_to_monitor))}, [287] = {"logpath" , SAY_DEPRECATED(false, "Use --log-file instead." , STRING(logpath))}, [289] = {"opengl" , SAY_DEPRECATED(false, "Use --backend=glx instead." , FIXED(backend, BKEND_GLX))}, [305] = {"shadow-exclude-reg" , SAY_DEPRECATED(true, "Use --clip-shadow-above instead." , REJECT(required_argument))}, #undef CLEAR_SHADOW_DEPRECATION #undef MENU_OPACITY_DEPRECATION }; // clang-format on static void setup_longopts(void) { auto opts = ccalloc(ARR_SIZE(picom_options) + 1, struct option); int option_count = 0; for (size_t i = 0; i < ARR_SIZE(picom_options); i++) { if (picom_options[i].arg.handler == NULL) { continue; } opts[option_count].name = picom_options[i].long_name; opts[option_count].has_arg = picom_options[i].has_arg; opts[option_count].flag = NULL; opts[option_count].val = (int)i; option_count++; } longopts = opts; } void print_help(const char *help, size_t indent, size_t curr_indent, size_t line_wrap, FILE *f) { if (curr_indent > indent) { fputs("\n", f); curr_indent = 0; } if (line_wrap - indent <= 1) { line_wrap = indent + 2; } size_t pos = 0; size_t len = strlen(help); while (pos < len) { fprintf(f, "%*s", (int)(indent - curr_indent), ""); curr_indent = 0; size_t towrite = line_wrap - indent; while (help[pos] == ' ') { pos++; } if (pos + towrite > len) { towrite = len - pos; fwrite(help + pos, 1, towrite, f); } else { auto space_break = towrite; while (space_break > 0 && help[pos + space_break - 1] != ' ') { space_break--; } bool print_hyphen = false; if (space_break == 0) { print_hyphen = true; towrite--; } else { towrite = space_break; } fwrite(help + pos, 1, towrite, f); if (print_hyphen) { fputs("-", f); } } fputs("\n", f); pos += towrite; } } /** * Print usage text. */ static void usage(const char *argv0, int ret) { FILE *f = (ret ? stderr : stdout); fprintf(f, "picom (%s)\n", PICOM_VERSION); fprintf(f, "Standalone X11 compositor\n"); fprintf(f, "Please report bugs to https://github.com/yshui/picom\n\n"); fprintf(f, "Usage: %s [OPTION]...\n\n", argv0); fprintf(f, "OPTIONS:\n"); int line_wrap = 80; struct winsize window_size = {0}; if (ioctl(fileno(f), TIOCGWINSZ, &window_size) != -1) { line_wrap = window_size.ws_col; } size_t help_indent = 0; for (size_t i = 0; i < ARR_SIZE(picom_options); i++) { if (picom_options[i].help == NULL) { // Hide options with no help message. continue; } auto option_len = strlen(picom_options[i].long_name) + 2 + 4; if (picom_options[i].arg.name) { option_len += strlen(picom_options[i].arg.name) + 1; } if (option_len > help_indent && option_len < 30) { help_indent = option_len; } } help_indent += 6; for (size_t i = 0; i < ARR_SIZE(picom_options); i++) { if (picom_options[i].help == NULL) { continue; } size_t option_len = 8; fprintf(f, " "); if ((i > 'a' && i < 'z') || (i > 'A' && i < 'Z')) { fprintf(f, "-%c, ", (char)i); } else { fprintf(f, " "); } fprintf(f, "--%s", picom_options[i].long_name); option_len += strlen(picom_options[i].long_name) + 2; if (picom_options[i].arg.name) { fprintf(f, "=%s", picom_options[i].arg.name); option_len += strlen(picom_options[i].arg.name) + 1; } fprintf(f, " "); option_len += 2; print_help(picom_options[i].help, help_indent, option_len, (size_t)line_wrap, f); } } static void set_default_winopts(options_t *opt) { auto mask = opt->wintype_option_mask; // Apply default wintype options. if (!mask[WINTYPE_DESKTOP].shadow) { // Desktop windows are always drawn without shadow by default. mask[WINTYPE_DESKTOP].shadow = true; opt->wintype_option[WINTYPE_DESKTOP].shadow = false; } // Focused/unfocused state only apply to a few window types, all other windows // are always considered focused. const wintype_t nofocus_type[] = {WINTYPE_UNKNOWN, WINTYPE_NORMAL, WINTYPE_UTILITY}; for (unsigned long i = 0; i < ARR_SIZE(nofocus_type); i++) { if (!mask[nofocus_type[i]].focus) { mask[nofocus_type[i]].focus = true; opt->wintype_option[nofocus_type[i]].focus = false; } } for (unsigned long i = 0; i < NUM_WINTYPES; i++) { if (!mask[i].shadow) { mask[i].shadow = true; opt->wintype_option[i].shadow = opt->shadow_enable; } if (!mask[i].fade) { mask[i].fade = true; opt->wintype_option[i].fade = opt->fading_enable; } if (!mask[i].focus) { mask[i].focus = true; opt->wintype_option[i].focus = true; } if (!mask[i].blur_background) { mask[i].blur_background = true; opt->wintype_option[i].blur_background = opt->blur_method != BLUR_METHOD_NONE; } if (!mask[i].full_shadow) { mask[i].full_shadow = true; opt->wintype_option[i].full_shadow = false; } if (!mask[i].redir_ignore) { mask[i].redir_ignore = true; opt->wintype_option[i].redir_ignore = false; } if (!mask[i].opacity) { mask[i].opacity = true; // Opacity is not set to a concrete number here because the // opacity logic is complicated, and needs an "unset" state opt->wintype_option[i].opacity = NAN; } if (!mask[i].clip_shadow_above) { mask[i].clip_shadow_above = true; opt->wintype_option[i].clip_shadow_above = false; } } } static const char *shortopts = "D:I:O:r:o:m:l:t:i:e:hcfCzGb"; /// Get config options that are needed to parse the rest of the options /// Return true if we should quit bool get_early_config(int argc, char *const *argv, char **config_file, bool *all_xerrors, bool *fork, int *exit_code) { setup_longopts(); int o = 0, longopt_idx = -1; // Pre-parse the command line arguments to check for --config and invalid // switches // Must reset optind to 0 here in case we reread the command line // arguments optind = 1; *config_file = NULL; *exit_code = 0; while (-1 != (o = getopt_long(argc, argv, shortopts, longopts, &longopt_idx))) { if (o == 256) { *config_file = strdup(optarg); } else if (o == 'h') { usage(argv[0], 0); return true; } else if (o == 'b') { *fork = true; } else if (o == 314) { *all_xerrors = true; } else if (o == 318) { printf("%s\n", PICOM_VERSION); return true; } else if (o == '?' || o == ':') { usage(argv[0], 1); goto err; } } // Check for abundant positional arguments if (optind < argc) { // log is not initialized here yet fprintf(stderr, "picom doesn't accept positional arguments.\n"); goto err; } return false; err: *exit_code = 1; return true; } /** * Process arguments and configuration files. */ bool get_cfg(options_t *opt, int argc, char *const *argv) { int o = 0, longopt_idx = -1; bool failed = false; optind = 1; while (-1 != (o = getopt_long(argc, argv, shortopts, longopts, &longopt_idx))) { if (o == '?' || o == ':' || picom_options[o].arg.handler == NULL) { usage(argv[0], 1); failed = true; } else if (!picom_options[o].arg.handler( &picom_options[o], &picom_options[o].arg, optarg, opt)) { failed = true; } if (failed) { // Parsing this option has failed, break the loop break; } } if (failed) { return false; } log_set_level_tls(opt->log_level); if (opt->monitor_repaint && opt->backend != BKEND_XRENDER && opt->legacy_backends) { log_warn("--monitor-repaint has no effect when backend is not xrender"); } if (opt->window_shader_fg) { scoped_charp cwd = getcwd(NULL, 0); scoped_charp tmp = opt->window_shader_fg; opt->window_shader_fg = locate_auxiliary_file("shaders", tmp, cwd); if (!opt->window_shader_fg) { log_error("Couldn't find the specified window shader file \"%s\"", tmp); return false; } } if (opt->write_pid_path && *opt->write_pid_path != '/') { log_warn("--write-pid-path is not an absolute path"); } if (opt->backend == BKEND_EGL) { if (opt->legacy_backends) { log_error("The egl backend is not supported with " "--legacy-backends"); return false; } log_warn("The egl backend is still experimental, use with care."); } if (!opt->legacy_backends && !backend_list[opt->backend]) { log_error("Backend \"%s\" is only available as part of the legacy " "backends.", BACKEND_STRS[opt->backend]); return false; } if (opt->debug_mode && opt->legacy_backends) { log_error("Debug mode does not work with the legacy backends."); return false; } if (opt->transparent_clipping && opt->legacy_backends) { log_error("Transparent clipping does not work with the legacy " "backends"); return false; } if (opt->glx_fshader_win_str && !opt->legacy_backends) { log_warn("--glx-fshader-win has been replaced by " "\"--window-shader-fg\" for the new backends."); } if (opt->window_shader_fg || opt->window_shader_fg_rules) { if (opt->backend == BKEND_XRENDER || opt->legacy_backends) { log_warn(opt->backend == BKEND_XRENDER ? "Shader interface is not supported by the xrender " "backend." : "The new shader interface is not supported by the " "legacy glx backend. You may want to use " "--glx-fshader-win instead."); opt->window_shader_fg = NULL; c2_list_free(&opt->window_shader_fg_rules, free); } } // Range checking and option assignments if (opt->max_brightness < 1.0) { if (opt->backend == BKEND_XRENDER || opt->legacy_backends) { log_warn("--max-brightness is not supported by the %s backend. " "Falling back to 1.0.", opt->backend == BKEND_XRENDER ? "xrender" : "legacy glx"); opt->max_brightness = 1.0; } else if (opt->use_damage) { log_warn("--max-brightness requires --no-use-damage. Falling " "back to 1.0."); opt->max_brightness = 1.0; } } // --blur-background-frame implies --blur-background if (opt->blur_background_frame && opt->blur_method == BLUR_METHOD_NONE) { opt->blur_method = BLUR_METHOD_KERNEL; } // Apply default wintype options that are dependent on global options set_default_winopts(opt); // Determine whether we track window grouping if (opt->detect_transient || opt->detect_client_leader) { opt->track_leader = true; } // Fill default blur kernel if (opt->blur_method == BLUR_METHOD_KERNEL && (!opt->blur_kerns || !opt->blur_kerns[0])) { opt->blur_kerns = parse_blur_kern_lst("3x3box", &opt->blur_kernel_count); CHECK(opt->blur_kerns); CHECK(opt->blur_kernel_count); } // Sanitize parameters for dual-filter kawase blur if (opt->blur_method == BLUR_METHOD_DUAL_KAWASE) { if (opt->blur_strength <= 0 && opt->blur_radius > 500) { log_warn("Blur radius >500 not supported by dual_kawase method, " "capping to 500."); opt->blur_radius = 500; } if (opt->blur_strength > 20) { log_warn("Blur strength >20 not supported by dual_kawase method, " "capping to 20."); opt->blur_strength = 20; } if (opt->legacy_backends) { log_warn("Dual-kawase blur is not implemented by the legacy " "backends."); } } if (opt->resize_damage < 0) { log_warn("Negative --resize-damage will not work correctly."); } if (opt->backend == BKEND_XRENDER) { for (int i = 0; i < opt->blur_kernel_count; i++) { auto kernel = opt->blur_kerns[i]; for (int j = 0; j < kernel->h * kernel->w; j++) { if (kernel->data[j] < 0) { log_warn("A convolution kernel with negative " "values may not work properly under X " "Render backend."); goto check_end; } } } check_end:; } if (opt->legacy_backends && opt->number_of_scripts > 0) { log_warn("Custom animations are not supported by the legacy " "backends. Disabling animations."); for (size_t i = 0; i < ARR_SIZE(opt->animations); i++) { opt->animations[i].script = NULL; } for (int i = 0; i < opt->number_of_scripts; i++) { script_free(opt->all_scripts[i]); } free(opt->all_scripts); opt->all_scripts = NULL; opt->number_of_scripts = 0; } generate_fading_config(opt); return true; } void options_postprocess_c2_lists(struct c2_state *state, struct x_connection *c, struct options *option) { if (!(c2_list_postprocess(state, c->c, option->unredir_if_possible_blacklist) && c2_list_postprocess(state, c->c, option->paint_blacklist) && c2_list_postprocess(state, c->c, option->shadow_blacklist) && c2_list_postprocess(state, c->c, option->shadow_clip_list) && c2_list_postprocess(state, c->c, option->fade_blacklist) && c2_list_postprocess(state, c->c, option->blur_background_blacklist) && c2_list_postprocess(state, c->c, option->invert_color_list) && c2_list_postprocess(state, c->c, option->window_shader_fg_rules) && c2_list_postprocess(state, c->c, option->opacity_rules) && c2_list_postprocess(state, c->c, option->rounded_corners_blacklist) && c2_list_postprocess(state, c->c, option->corner_radius_rules) && c2_list_postprocess(state, c->c, option->focus_blacklist) && c2_list_postprocess(state, c->c, option->transparent_clipping_blacklist))) { log_error("Post-processing of conditionals failed, some of your rules " "might not work"); } } void options_destroy(struct options *options) { // Free blacklists c2_list_free(&options->shadow_blacklist, NULL); c2_list_free(&options->shadow_clip_list, NULL); c2_list_free(&options->fade_blacklist, NULL); c2_list_free(&options->focus_blacklist, NULL); c2_list_free(&options->invert_color_list, NULL); c2_list_free(&options->blur_background_blacklist, NULL); c2_list_free(&options->opacity_rules, NULL); c2_list_free(&options->paint_blacklist, NULL); c2_list_free(&options->unredir_if_possible_blacklist, NULL); c2_list_free(&options->rounded_corners_blacklist, NULL); c2_list_free(&options->corner_radius_rules, NULL); c2_list_free(&options->window_shader_fg_rules, free); c2_list_free(&options->transparent_clipping_blacklist, NULL); free(options->write_pid_path); free(options->logpath); for (int i = 0; i < options->blur_kernel_count; ++i) { free(options->blur_kerns[i]); } free(options->blur_kerns); free(options->glx_fshader_win_str); for (int i = 0; i < options->number_of_scripts; i++) { script_free(options->all_scripts[i]); options->all_scripts[i] = NULL; } free(options->all_scripts); memset(options->animations, 0, sizeof(options->animations)); } // vim: set noet sw=8 ts=8 :