diff --git a/src/core/output-layout.cpp b/src/core/output-layout.cpp index 1aeb6be..d481851 100644 --- a/src/core/output-layout.cpp +++ b/src/core/output-layout.cpp @@ -1411,6 +1411,38 @@ class output_layout_t::impl wf::option_wrapper_t stitch_enabled{"core/stitch_enabled"}; wf::option_wrapper_t stitch_outputs{"core/stitch_outputs"}; + /* Inputs and result of the last stitched reconfiguration, so that a + * config reload which changes nothing relevant does not rebuild the + * group and re-scope every view. */ + struct applied_stitch_t + { + std::string members; + int max_buffer_size; + output_configuration_t requested; + output_configuration_t applied; + std::string scope_settings; + }; + std::optional applied_stitch; + + /** Options read by window scoping, which reload re-applies to views. */ + std::string window_scope_settings() + { + std::string key; + for (auto name : {"core/window_scope", "core/window_scope_rules", "grid/zone_gap"}) + { + auto opt = get_core().config->get_option(name); + key += (opt ? opt->get_value_str() : "") + '\n'; + } + + for (auto& [handle, lo] : outputs) + { + auto opt = lo->config_section ? lo->config_section->get_option_or("zone_gap") : nullptr; + key += std::string(handle->name) + '=' + (opt ? opt->get_value_str() : "") + '\n'; + } + + return key; + } + void release_mirror_buffers() { // A source may be removed in the same transaction. Release its cursor @@ -1805,6 +1837,11 @@ class output_layout_t::impl } outputs.erase(to_remove); + if (applied_stitch) + { + applied_stitch->requested.erase(to_remove); + applied_stitch->applied.erase(to_remove); + } /* If no physical outputs, then at least the noop output */ assert(get_outputs().size() || is_shutting_down()); @@ -1992,7 +2029,31 @@ class output_layout_t::impl { configuration[output].source = OUTPUT_IMAGE_SOURCE_NONE; } + } + + std::string members = stitch_outputs; + int max_buffer_size = wf::option_wrapper_t("workarounds/max_buffer_size"); + if (stitch_output && !removing && stitch_enabled && applied_stitch && + (applied_stitch->members == members) && (applied_stitch->max_buffer_size == max_buffer_size) && + (applied_stitch->requested == configuration) && + (applied_stitch->applied == get_current_configuration())) + { + // Group, member slices and all other outputs are already as + // configured. Only re-scope views if window scope options changed. + auto scope_settings = window_scope_settings(); + if (applied_stitch->scope_settings != scope_settings) + { + applied_stitch->scope_settings = std::move(scope_settings); + rescope_views(scoped_views); + } + return; + } + + applied_stitch.reset(); + auto requested = configuration; + for (auto& [output, layout_output] : this->outputs) + { layout_output->stitch_source_box.reset(); } @@ -2004,11 +2065,13 @@ class output_layout_t::impl // The public mirror state does not include source rectangles; reapply // active groups even when only member order or slice geometry changed. + bool applied = false; if (stitch_output || configuration != get_current_configuration()) { if (test_configuration(configuration)) { apply_configuration(configuration); + applied = true; } } @@ -2022,6 +2085,12 @@ class output_layout_t::impl LOGI("Combined desktop: ", stitch_output->width, "x", stitch_output->height, " on VIRTUAL-1"); } + + if (applied && wo) + { + applied_stitch = applied_stitch_t{members, max_buffer_size, std::move(requested), + get_current_configuration(), window_scope_settings()}; + } } else { for (auto& [handle, lo] : outputs) @@ -2032,6 +2101,11 @@ class output_layout_t::impl destroy_stitch_output(); } + rescope_views(scoped_views); + } + + void rescope_views(std::vector>& scoped_views) + { for (auto& [view, workspace] : scoped_views) { if (!view->is_mapped() || !view->get_output()) continue; @@ -2339,6 +2413,16 @@ class output_layout_t::impl wlr_configuration); } + void set_stitch_member_power(output_layout_output_t& lo, bool enabled) + { + lo.stitch_power_off = !enabled; + lo.apply_state(lo.current_state); + if (auto source = find_output(lo.current_state.mirror_from)) + { + source->render->damage_whole(); + } + } + void set_power_mode(wlr_output_power_v1_set_mode_event *ev) { LOGD("output: ", ev->output->name, " power mode: ", ev->mode); @@ -2358,7 +2442,10 @@ class output_layout_t::impl } if (stitch_output && outputs.at(target)->stitch_source_box) { - target = stitch_output; + // Power only this member's slice. wlroots reports the member's + // new mode to clients when the connector commits enabled/disabled. + set_stitch_member_power(*outputs.at(target), !wants_dpms); + return; } config[target].source = (wants_dpms ? OUTPUT_IMAGE_SOURCE_DPMS : OUTPUT_IMAGE_SOURCE_SELF); @@ -2386,13 +2473,7 @@ class output_layout_t::impl if (lo->stitch_source_box) { - lo->stitch_power_off = !enabled; - lo->apply_state(lo->current_state); - if (auto source = find_output(lo->current_state.mirror_from)) - { - source->render->damage_whole(); - } - + set_stitch_member_power(*lo, enabled); return true; }