picom/src/options.c

958 lines
45 KiB
C

// SPDX-License-Identifier: MPL-2.0
// Copyright (c) Yuxuan Shui <yshuiv7@gmail.com>
#include <getopt.h>
#include <locale.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <termios.h>
#include <unistd.h>
#include <xcb/render.h> // 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 :