Review follow-ups for the r71 patches (apply after r71-stitch-noop-reload). 1. output-layout: stitched members kept one stitch_power_off flag for both power owners, so wlr-output-power requests and plugin calls through output_layout_t::set_output_power() (digitalrain-power) undid each other. Keep a flag per source; the member's slice is off while either asks. 2. vswitch: vswitch/set-workspace over IPC returned ok when the switch was refused (plugin exclusivity); return an error instead. 3. vswitch: the fallback slide (no cube) set the workspace before starting the wall, so the first frame showed the destination. Point the wall at the old position as soon as the slide starts. 4. vswitch: when columns wrap, slide by add_direction()'s shortest delta instead of the raw workspace difference (fallback and active slides). 5. ipc: a client whose send queue never fully drained kept every sent byte. Drop the sent prefix once it is at least 64 KiB and half of the queue. 6. window-scope: mark fullscreen requests from an explicit window-scope reflow like tile requests, so tile moves the node to that workspace. No installed header or class layout changes; plugin ABI is unchanged. diff --git a/plugins/ipc/ipc.cpp b/plugins/ipc/ipc.cpp index 9d0b3d4..fd4f63f 100644 --- a/plugins/ipc/ipc.cpp +++ b/plugins/ipc/ipc.cpp @@ -149,6 +149,8 @@ int wl_loop_handle_ipc_client_fd_event(int, uint32_t mask, void *data) static constexpr int MAX_MESSAGE_LEN = (1 << 20); static constexpr int HEADER_LEN = 4; static constexpr size_t MAX_OUTPUT_QUEUE = 16u * 1024 * 1024; +/** Sent bytes kept at the front of a client's output queue before compacting. */ +static constexpr size_t OUTPUT_COMPACT_MIN = 64u * 1024; wf::ipc::client_t::client_t(server_t *ipc, int fd) { @@ -335,6 +337,15 @@ void wf::ipc::client_t::flush_output() { // Reader is slow: keep the remainder queued and flush when writable // instead of dropping the connection on transient backpressure. + // Drop the sent prefix once it is most of the buffer, so a reader + // which never fully catches up does not grow it without bound. + // Copying at most out_sent bytes keeps this amortized linear. + if ((out_sent >= OUTPUT_COMPACT_MIN) && (out_sent >= out_buffer.size() / 2)) + { + out_buffer.erase(out_buffer.begin(), out_buffer.begin() + out_sent); + out_sent = 0; + } + set_writable_watch(true); return; } diff --git a/plugins/vswitch/vswitch.cpp b/plugins/vswitch/vswitch.cpp index 3ad44e8..0cfd201 100644 --- a/plugins/vswitch/vswitch.cpp +++ b/plugins/vswitch/vswitch.cpp @@ -164,10 +164,18 @@ class workspace_switch_t */ virtual void set_target_workspace(point_t workspace) { - point_t cws = output->wset()->get_current_workspace(); + set_target_workspace(workspace, workspace - output->wset()->get_current_workspace()); + } - animation.dx.set(animation.dx + cws.x - workspace.x, 0); - animation.dy.set(animation.dy + cws.y - workspace.y, 0); + /** + * Like set_target_workspace(workspace), but animate by @delta workspaces, + * which differs from the difference to the current workspace when the + * columns wrap around. + */ + void set_target_workspace(point_t workspace, point_t delta) + { + animation.dx.set(animation.dx - delta.x, 0); + animation.dy.set(animation.dy - delta.y, 0); animation.start(); std::vector fixed_views; @@ -293,7 +301,8 @@ class workspace_switch_t tmanager->end_transform_update(); } - wf::effect_hook_t post_render = [=] () + /** Point the wall at the current workspace, offset by the animation. */ + void update_wall_viewport() { auto start = wall->get_workspace_rectangle( output->wset()->get_current_workspace()); @@ -305,6 +314,11 @@ class workspace_switch_t start.height, }; wall->set_viewport(viewport); + } + + wf::effect_hook_t post_render = [=] () + { + update_wall_viewport(); update_overlay_fb(); output->render->damage_whole(); @@ -387,13 +401,19 @@ class vswitch : public wf::per_output_plugin_instance_t on_done(); } - /** The workspace has already changed: slide in from @old_ws. */ - void slide_from(wf::point_t old_ws) + /** + * The workspace has already changed by @delta (the shortest way when + * columns wrap): slide in from the previous workspace. + */ + void slide_from(wf::point_t delta) { - auto cws = output->wset()->get_current_workspace(); - animation.dx.set(old_ws.x - cws.x, 0); - animation.dy.set(old_ws.y - cws.y, 0); + animation.dx.set(-delta.x, 0); + animation.dy.set(-delta.y, 0); animation.start(); + // post_render only moves the wall after a frame is drawn; start + // at the old position so the first frame does not show the + // destination. + update_wall_viewport(); } private: @@ -701,7 +721,7 @@ class vswitch : public wf::per_output_plugin_instance_t // Without a cube to animate it, use vswitch's own slide. if (!ev.carried_out && start_switch()) { - algorithm->slide_from(cws); + algorithm->slide_from(animation_delta); } return true; @@ -713,7 +733,7 @@ class vswitch : public wf::per_output_plugin_instance_t } algorithm->set_overlay_view(toplevel_cast(view)); - algorithm->set_target_workspace(target_ws); + algorithm->set_target_workspace(target_ws, animation_delta); return true; } @@ -902,12 +922,13 @@ class wf_vswitch_global_plugin_t : public wf::per_output_plugin_t switch_with_view = view; } - if (output_instance[wo]->set_capabilities(wf::CAPABILITY_MANAGE_COMPOSITOR)) + wf::point_t new_viewport = {int(x), int(y)}; + wf::point_t cur_viewport = wo->wset()->get_current_workspace(); + wf::point_t delta = new_viewport - cur_viewport; + if (!output_instance[wo]->set_capabilities(wf::CAPABILITY_MANAGE_COMPOSITOR) || + !output_instance[wo]->add_direction(delta, switch_with_view)) { - wf::point_t new_viewport = {int(x), int(y)}; - wf::point_t cur_viewport = wo->wset()->get_current_workspace(); - wf::point_t delta = new_viewport - cur_viewport; - output_instance[wo]->add_direction(delta, switch_with_view); + return wf::ipc::json_error("workspace switch refused"); } return wf::ipc::json_ok(); diff --git a/src/core/output-layout.cpp b/src/core/output-layout.cpp index d481851..23c0406 100644 --- a/src/core/output-layout.cpp +++ b/src/core/output-layout.cpp @@ -478,7 +478,17 @@ struct output_layout_output_t std::optional stitch_source_box; std::optional stitch_restore_state; std::vector stitch_zones; - bool stitch_power_off = false; + /* A stitched member has two independent power owners: plugins through + * output_layout_t::set_output_power() (digitalrain-power), and clients of + * wlr-output-power-management. Each only changes its own flag; the slice + * is off while either asks for it. */ + bool stitch_power_off_plugin = false; + bool stitch_power_off_protocol = false; + + bool stitch_power_off() const + { + return stitch_power_off_plugin || stitch_power_off_protocol; + } std::unique_ptr output; wl_listener_wrapper on_destroy, on_commit, on_request_state; @@ -1168,7 +1178,7 @@ struct output_layout_output_t void setup_mirror() { - if (stitch_source_box && stitch_power_off) + if (stitch_source_box && stitch_power_off()) { set_enabled(false); return; @@ -1333,7 +1343,7 @@ struct output_layout_output_t teardown_mirror(); if (state.source == OUTPUT_IMAGE_SOURCE_NONE || state.source == OUTPUT_IMAGE_SOURCE_MASKED_DPMS || - (stitch_source_box && stitch_power_off)) + (stitch_source_box && stitch_power_off())) { /* output is OFF */ destroy_wayfire_output(); @@ -2096,7 +2106,8 @@ class output_layout_t::impl for (auto& [handle, lo] : outputs) { lo->stitch_restore_state.reset(); - lo->stitch_power_off = false; + lo->stitch_power_off_plugin = false; + lo->stitch_power_off_protocol = false; } destroy_stitch_output(); @@ -2413,9 +2424,9 @@ class output_layout_t::impl wlr_configuration); } - void set_stitch_member_power(output_layout_output_t& lo, bool enabled) + void set_stitch_member_power(output_layout_output_t& lo, bool enabled, bool from_protocol) { - lo.stitch_power_off = !enabled; + (from_protocol ? lo.stitch_power_off_protocol : lo.stitch_power_off_plugin) = !enabled; lo.apply_state(lo.current_state); if (auto source = find_output(lo.current_state.mirror_from)) { @@ -2444,7 +2455,7 @@ class output_layout_t::impl { // 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); + set_stitch_member_power(*outputs.at(target), !wants_dpms, true); return; } @@ -2473,7 +2484,7 @@ class output_layout_t::impl if (lo->stitch_source_box) { - set_stitch_member_power(*lo, enabled); + set_stitch_member_power(*lo, enabled, false); return true; } diff --git a/src/core/window-scope.cpp b/src/core/window-scope.cpp index 74749d7..8ddd27d 100644 --- a/src/core/window-scope.cpp +++ b/src/core/window-scope.cpp @@ -211,7 +211,12 @@ void apply_window_scope(wayfire_toplevel_view view, std::optional w auto grid = output->wset()->get_workspace_grid_size(); ws.x = std::clamp(ws.x, 0, grid.width - 1); ws.y = std::clamp(ws.y, 0, grid.height - 1); - if (view->pending_fullscreen()) get_core().default_wm->fullscreen_request(view, output, true, ws); + if (view->pending_fullscreen()) + { + if (workspace) view->get_data_safe(); + get_core().default_wm->fullscreen_request(view, output, true, ws); + if (workspace) view->erase_data(); + } else if (view->pending_tiled_edges()) { if (workspace) view->get_data_safe();