config/output: Store output configs sequentially

The current output configuration system merges configurations such that
only one configuration per match is stored. This is a simplification of
a previous design and has the benefit that a minimal number of output
configurations are retained, but to correctly merge and supersede
configurations it is required that an identifier can be resolved from a
connector, which leads to differences in how output configurations are
interpreted based on whether a display is currently connected or not.

Instead, append all new output configurations to the list as they are
made, and remove old output configurations once all their settings have
been overwritten by newer configurations.

Based on ideas from https://github.com/swaywm/sway/pull/5629

Fixes: https://github.com/swaywm/sway/issues/5632
This commit is contained in:
Kenny Levinsen 2025-04-29 14:02:36 +02:00
parent 7e7994dbb2
commit 4225cebdf9

View file

@ -82,6 +82,30 @@ struct output_config *new_output_config(const char *name) {
return oc; return oc;
} }
static bool empty_output_config(struct output_config *oc) {
return oc->enabled == -1 &&
oc->width == -1 &&
oc->height == -1 &&
oc->x == INT_MAX &&
oc->y == INT_MAX &&
oc->scale == -1 &&
oc->scale_filter == SCALE_FILTER_DEFAULT &&
oc->subpixel == WL_OUTPUT_SUBPIXEL_UNKNOWN &&
oc->refresh_rate == -1 &&
oc->custom_mode == -1 &&
oc->drm_mode.type == (uint32_t)-1 &&
oc->transform == -1 &&
oc->max_render_time == -1 &&
oc->adaptive_sync == -1 &&
oc->render_bit_depth == RENDER_BIT_DEPTH_DEFAULT &&
oc->set_color_transform == false &&
oc->background == NULL &&
oc->background_option == NULL &&
oc->background_fallback == NULL &&
oc->power == -1 &&
oc->allow_tearing == -1;
}
// supersede_output_config clears all fields in dst that were set in src // supersede_output_config clears all fields in dst that were set in src
static void supersede_output_config(struct output_config *dst, struct output_config *src) { static void supersede_output_config(struct output_config *dst, struct output_config *src) {
if (src->enabled != -1) { if (src->enabled != -1) {
@ -232,57 +256,37 @@ static void merge_output_config(struct output_config *dst, struct output_config
} }
void store_output_config(struct output_config *oc) { void store_output_config(struct output_config *oc) {
bool merged = false;
bool wildcard = strcmp(oc->name, "*") == 0; bool wildcard = strcmp(oc->name, "*") == 0;
struct sway_output *output = wildcard ? NULL : all_output_by_name_or_id(oc->name);
char id[128]; // Supersede any existing configurations that match by clearing settings
if (output) { // that can no longer be reached anyway, which allows us to later garbage
output_get_identifier(id, sizeof(id), output); // collect configurations that no longer have any value
struct output_config *old = NULL;
for (int idx = 0; idx < config->output_configs->length; idx++) {
old = config->output_configs->items[idx];
if (wildcard || strcmp(old->name, oc->name) == 0) {
supersede_output_config(old, oc);
}
} }
for (int i = 0; i < config->output_configs->length; i++) { // As a very minor optimization, if the last config is for the same name,
struct output_config *old = config->output_configs->items[i]; // we can merge with that instead of adding a new config, reducing the
// number of configs when sequentially changing different settings
// If the old config matches the new config's name, regardless of if (old != NULL && strcmp(old->name, oc->name) == 0) {
// whether it was name or identifier, merge on top of the existing
// config. If the new config is a wildcard, this also merges on top of
// old wildcard configs.
if (strcmp(old->name, oc->name) == 0) {
merge_output_config(old, oc); merge_output_config(old, oc);
merged = true;
continue;
}
// If the new config is a wildcard config we supersede all non-wildcard
// configs. Old wildcard configs have already been handled above.
if (wildcard) {
supersede_output_config(old, oc);
continue;
}
// If the new config matches an output's name, and the old config
// matches on that output's identifier, supersede it.
if (output && strcmp(old->name, id) == 0 &&
strcmp(oc->name, output->wlr_output->name) == 0) {
supersede_output_config(old, oc);
}
}
sway_log(SWAY_DEBUG, "Config stored for output %s (enabled: %d) (%dx%d@%fHz "
"position %d,%d scale %f subpixel %s transform %d) (bg %s %s) (power %d) "
"(max render time: %d) (allow tearing: %d)",
oc->name, oc->enabled, oc->width, oc->height, oc->refresh_rate,
oc->x, oc->y, oc->scale, sway_wl_output_subpixel_to_string(oc->subpixel),
oc->transform, oc->background, oc->background_option, oc->power,
oc->max_render_time, oc->allow_tearing);
// If the configuration was not merged into an existing configuration, add
// it to the list. Otherwise we're done with it and can free it.
if (!merged) {
list_add(config->output_configs, oc);
} else {
free_output_config(oc); free_output_config(oc);
} else {
list_add(config->output_configs, oc);
}
// Remove any configurations that have had all their settings removed
for (int idx = 0; idx < config->output_configs->length; idx++) {
old = config->output_configs->items[idx];
if (empty_output_config(old)) {
list_del(config->output_configs, idx);
free_output_config(old);
idx--;
}
} }
} }
@ -588,14 +592,13 @@ static struct output_config *find_output_config_from_list(
char id[128]; char id[128];
output_get_identifier(id, sizeof(id), sway_output); output_get_identifier(id, sizeof(id), sway_output);
// We take a new config and merge on top, in order, the wildcard config, // To create the output config for this output, we merge all configurations
// output config by name, and output config by identifier to form the final // that match the output in the order they were stored, such that later
// config. If there are multiple matches, they are merged in order. // configurations override earlier ones
struct output_config *oc = NULL; for (size_t idx = 0; idx < configs_len; idx++) {
struct output_config *oc = configs[idx];
const char *names[] = {"*", name, id, NULL}; const char *names[] = {"*", name, id, NULL};
for (const char **name = &names[0]; *name; name++) { for (const char **name = &names[0]; *name; name++) {
for (size_t idx = 0; idx < configs_len; idx++) {
oc = configs[idx];
if (strcmp(oc->name, *name) == 0) { if (strcmp(oc->name, *name) == 0) {
merge_output_config(result, oc); merge_output_config(result, oc);
} }