1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
|
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<wayfire_toplevel_view> 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<vswitch>
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<wlr_box> stitch_source_box;
std::optional<output_state_t> stitch_restore_state;
std::vector<wf::output_stitch_zone_t> 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<wf::output_impl_t> 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<wf::point_t> 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<window_scope_reflow_t>();
+ get_core().default_wm->fullscreen_request(view, output, true, ws);
+ if (workspace) view->erase_data<window_scope_reflow_t>();
+ }
else if (view->pending_tiled_edges())
{
if (workspace) view->get_data_safe<window_scope_reflow_t>();
|