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
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
|
Pyr r85: monitor reservations. A plugin (drawers) can keep space free along the edges of one monitor
of a stitched output (a stitch zone) or of a plain output; maximize, slots, zones, placement and Move snap
previews then use the rest, and grid re-fits that monitor's slotted windows (only those that move) with its
crossfade. A window marked monitor_reservation_exempt_t (a drawer window) ignores reservations for itself.
API additions only (wayfire/window-scope.hpp); WAYFIRE_ABI unchanged. Without reservations nothing changes.
diff --git a/plugins/grid/grid.cpp b/plugins/grid/grid.cpp
index 46bf79e..4653bc0 100644
--- a/plugins/grid/grid.cpp
+++ b/plugins/grid/grid.cpp
@@ -457,6 +457,7 @@ class wayfire_grid : public wf::plugin_interface_t, public wf::per_output_tracke
void handle_new_output(wf::output_t *output) override
{
output->connect(&on_workarea_changed);
+ output->connect(&on_reservation_changed);
output->connect(&on_maximize_signal);
output->connect(&on_fullscreen_signal);
output->connect(&on_tiled);
@@ -598,6 +599,69 @@ class wayfire_grid : public wf::plugin_interface_t, public wf::per_output_tracke
}
};
+ /* A drawer (or another plugin) reserved or released space on one monitor: re-fit the slotted and
+ * zone-placed windows of that monitor. Windows whose slot does not move are left alone, so they do
+ * not crossfade. */
+ wf::signal::connection_t<wf::monitor_reservation_changed_signal> on_reservation_changed =
+ [=] (wf::monitor_reservation_changed_signal *ev)
+ {
+ auto output = ev->output;
+ auto layout = output->get_data<wf::output_stitch_layout_t>();
+ const bool zoned = layout && !layout->zones.empty();
+ auto screen = output->get_relative_geometry();
+ for (auto& view : output->wset()->get_views(wf::WSET_MAPPED_ONLY))
+ {
+ if (view->pending_fullscreen() || !can_adjust_view(view) || !get_view_wset(view) ||
+ view->has_data<wf::monitor_reservation_exempt_t>())
+ {
+ continue;
+ }
+
+ auto data = view->get_data<wf_grid_slot_data>();
+ auto wm = view->get_pending_geometry();
+ if (data && !data->zone.empty())
+ {
+ if ((data->zone != ev->monitor) || !view->pending_tiled_edges()) continue;
+ auto zone = find_zone(output, data->zone);
+ if (!zone) continue;
+ auto ws = get_view_wset(view)->get_current_workspace();
+ ws.x += std::floor((wm.x + wm.width / 2) / screen.width);
+ ws.y += std::floor((wm.y + wm.height / 2) / screen.height);
+ if (adjust_for_workspace(get_view_wset(view), zone->geometry, ws) != wm)
+ {
+ place_in_zone(view, *zone, ws);
+ }
+
+ continue;
+ }
+
+ if (zoned)
+ {
+ auto zone = wf::window_scope_zone(view);
+ if (!zone || (zone->name != ev->monitor)) continue;
+ } else if (ev->monitor != output->to_string())
+ {
+ continue;
+ }
+
+ int slot = data ? data->slot : 0;
+ /* Maximized outside the grid plugin (for example when it opened maximized). */
+ if (!slot && (view->pending_tiled_edges() == wf::TILED_EDGES_ALL) &&
+ (wm.width == ev->old_workarea.width) && (wm.height == ev->old_workarea.height))
+ {
+ slot = wf::grid::SLOT_CENTER;
+ }
+
+ if (!slot) continue;
+ wf::pointf_t delta{std::floor(1.0 * wm.x / screen.width) * screen.width,
+ std::floor(1.0 * wm.y / screen.height) * screen.height};
+ if (slot_dimensions(view, slot) + delta != wm)
+ {
+ handle_slot(view, slot, delta);
+ }
+ }
+ };
+
wf::geometry_t adjust_for_workspace(std::shared_ptr<wf::workspace_set_t> wset,
wf::geometry_t geometry, wf::point_t workspace)
{
diff --git a/plugins/single_plugins/move-snap-map.hpp b/plugins/single_plugins/move-snap-map.hpp
index a55249d..9bf0cc5 100644
--- a/plugins/single_plugins/move-snap-map.hpp
+++ b/plugins/single_plugins/move-snap-map.hpp
@@ -185,8 +185,14 @@ class move_snap_map_t
// Workarea changes are emitted on each output, not on core.
wf::signal::connection_t<wf::workarea_changed_signal> workarea_changed =
[this] (auto) { if (!owner.empty()) show(snapshot(scope)); };
+ wf::signal::connection_t<wf::monitor_reservation_changed_signal> reservation_changed =
+ [this] (auto) { if (!owner.empty()) show(snapshot(scope)); };
wf::signal::connection_t<wf::output_added_signal> output_added =
- [this] (wf::output_added_signal *ev) { ev->output->connect(&workarea_changed); };
+ [this] (wf::output_added_signal *ev)
+ {
+ ev->output->connect(&workarea_changed);
+ ev->output->connect(&reservation_changed);
+ };
public:
void init()
{
@@ -226,6 +232,7 @@ class move_snap_map_t
for (auto output : wf::get_core().output_layout->get_outputs())
{
output->connect(&workarea_changed);
+ output->connect(&reservation_changed);
}
}
void fini() { hide(); repository->unregister_method("move/snap-regions"); }
diff --git a/plugins/single_plugins/move-snap.hpp b/plugins/single_plugins/move-snap.hpp
index 18bc85d..32d276d 100644
--- a/plugins/single_plugins/move-snap.hpp
+++ b/plugins/single_plugins/move-snap.hpp
@@ -334,11 +334,12 @@ class move_snap_t : public wf::per_output_tracker_mixin_t<>
};
wf::signal::connection_t<wf::output_layout_configuration_changed_signal> on_layout = [this] (auto) { clear(); };
wf::signal::connection_t<wf::workarea_changed_signal> on_workarea = [this] (auto) { clear(); };
+ wf::signal::connection_t<wf::monitor_reservation_changed_signal> on_reservation = [this] (auto) { clear(); };
wf::signal::connection_t<wf::reload_config_signal> on_config = [this] (auto) { clear(); };
public:
void handle_new_output(wf::output_t *output) override
{
- output->connect(&on_tile); output->connect(&on_workarea);
+ output->connect(&on_tile); output->connect(&on_workarea); output->connect(&on_reservation);
}
void handle_output_removed(wf::output_t*) override { clear(); }
void init()
diff --git a/src/api/wayfire/window-scope.hpp b/src/api/wayfire/window-scope.hpp
index 73f8d3e..7ee5015 100644
--- a/src/api/wayfire/window-scope.hpp
+++ b/src/api/wayfire/window-scope.hpp
@@ -17,6 +17,43 @@ wf::geometry_t physical_zone_workarea(wf::output_t *output, const output_stitch_
/** The workarea outside physical zones. A plain output is one monitor, so it gets the same grid/zone_gap
* inset as a physical zone; the virtual scope of a combined output stays edge to edge. */
wf::geometry_t unzoned_workarea(wf::output_t *output);
+
+/** Space kept free along a monitor's edges, in logical pixels (for example by a drawer plugin). */
+struct monitor_reservation_t
+{
+ int top = 0, bottom = 0, left = 0, right = 0;
+ bool operator ==(const monitor_reservation_t& o) const
+ {
+ return top == o.top && bottom == o.bottom && left == o.left && right == o.right;
+ }
+};
+/** A monitor is a stitch zone (by name) of a combined output, or a plain output (output->to_string()).
+ * Sets @owner's reservation on @monitor; all zeros removes it. The reservations of all owners add up and shrink
+ * physical_zone_workarea() / unzoned_workarea() (and so maximize, slots, zones and placement) before the zone gap
+ * is applied; together they never leave less than a third of the monitor in either direction. Emits
+ * monitor_reservation_changed_signal on @output when the sum changes. */
+void set_monitor_reservation(wf::output_t *output, const std::string& monitor, const std::string& owner,
+ monitor_reservation_t reservation);
+/** The sum of all owners' reservations on @monitor. */
+monitor_reservation_t get_monitor_reservation(wf::output_t *output, const std::string& monitor);
+/** The monitor's area before gaps and reservations: its stitch zone within the output workarea, or the workarea
+ * of a plain output. Empty if @monitor is not on @output. */
+wf::geometry_t monitor_area(wf::output_t *output, const std::string& monitor);
+/** The area windows tile into on @monitor: monitor_area() minus reservations, inset by the zone gap. */
+wf::geometry_t monitor_workarea(wf::output_t *output, const std::string& monitor);
+/** grid/zone_gap, or the monitor's [output:NAME] zone_gap when that is not negative. */
+int monitor_gap(const std::string& monitor);
+/** Emitted on the output after the reservations of one of its monitors changed. */
+struct monitor_reservation_changed_signal
+{
+ wf::output_t *output;
+ std::string monitor;
+ /** monitor_workarea() before the change. */
+ wf::geometry_t old_workarea;
+};
+/** A window that lives in reserved space (a drawer): its own workarea ignores monitor reservations. */
+struct monitor_reservation_exempt_t : public wf::custom_data_t {};
+
void prepare_window_scope(wayfire_toplevel_view view);
void update_window_scope_after_move(wayfire_toplevel_view view);
void apply_window_scope(wayfire_toplevel_view view, std::optional<wf::point_t> workspace = {});
diff --git a/src/core/window-scope.cpp b/src/core/window-scope.cpp
index c7be4b6..bc1b55f 100644
--- a/src/core/window-scope.cpp
+++ b/src/core/window-scope.cpp
@@ -9,6 +9,7 @@
#include <wayfire/option-wrapper.hpp>
#include <wayfire/nonstd/json.hpp>
#include <fnmatch.h>
+#include <map>
#include <cmath>
#include <stdexcept>
@@ -155,8 +156,7 @@ std::optional<output_stitch_zone_t> window_scope_zone_peek(wayfire_toplevel_view
return {};
}
-/** Inset a monitor's workarea by grid/zone_gap, or by its [output:NAME] zone_gap when that is not negative. */
-static wf::geometry_t inset_by_zone_gap(wf::geometry_t area, const std::string& monitor)
+int monitor_gap(const std::string& monitor)
{
int gap = 0;
if (auto option = get_core().config->get_option("grid/zone_gap")) gap = std::stoi(option->get_value_str());
@@ -168,27 +168,136 @@ static wf::geometry_t inset_by_zone_gap(wf::geometry_t area, const std::string&
if (local >= 0) gap = local;
}
}
+ return gap;
+}
+/** Inset a monitor's workarea by grid/zone_gap, or by its [output:NAME] zone_gap when that is not negative. */
+static wf::geometry_t inset_by_zone_gap(wf::geometry_t area, const std::string& monitor)
+{
+ int gap = monitor_gap(monitor);
double gx = std::clamp<double>(gap, 0, std::max(0.0, std::floor((area.width - 1) / 2)));
double gy = std::clamp<double>(gap, 0, std::max(0.0, std::floor((area.height - 1) / 2)));
return {area.x + gx, area.y + gy, area.width - 2 * gx, area.height - 2 * gy};
}
-wf::geometry_t physical_zone_workarea(wf::output_t *output, const output_stitch_zone_t& zone)
+
+namespace
+{
+/** Per output: monitor -> owner -> reservation. */
+struct monitor_reservations_t : public custom_data_t
+{
+ std::map<std::string, std::map<std::string, monitor_reservation_t>> monitors;
+};
+monitor_reservation_t sum_reservations(wf::output_t *output, const std::string& monitor)
+{
+ monitor_reservation_t sum;
+ auto data = output ? output->get_data<monitor_reservations_t>() : nullptr;
+ if (!data || !data->monitors.count(monitor)) return sum;
+ for (auto& [owner, r] : data->monitors[monitor])
+ {
+ sum.top += std::max(0, r.top); sum.bottom += std::max(0, r.bottom);
+ sum.left += std::max(0, r.left); sum.right += std::max(0, r.right);
+ }
+ return sum;
+}
+/** Shrink a pair of reservations proportionally so at least a third of @length stays free. */
+void cap_pair(int& a, int& b, int length)
+{
+ const int most = length - (length + 2) / 3;
+ if (a + b <= most || a + b <= 0) return;
+ const double k = 1.0 * std::max(0, most) / (a + b);
+ a = std::floor(a * k); b = std::floor(b * k);
+}
+wf::geometry_t reserve(wf::geometry_t area, wf::output_t *output, const std::string& monitor)
+{
+ auto r = sum_reservations(output, monitor);
+ cap_pair(r.left, r.right, area.width);
+ cap_pair(r.top, r.bottom, area.height);
+ return {area.x + r.left, area.y + r.top, area.width - r.left - r.right, area.height - r.top - r.bottom};
+}
+wf::geometry_t zone_area(wf::output_t *output, const output_stitch_zone_t& zone)
{
auto area = geometry_intersection(zone.geometry, output->workarea->get_workarea());
if (area.width <= 0 || area.height <= 0) area = zone.geometry;
- return inset_by_zone_gap(area, zone.name);
+ return area;
}
-wf::geometry_t unzoned_workarea(wf::output_t *output)
+bool combined(wf::output_t *output)
{
- auto area = output->workarea->get_workarea();
auto layout = output->get_data<output_stitch_layout_t>();
- if (layout && !layout->zones.empty()) return area;
- return inset_by_zone_gap(area, output->to_string());
+ return layout && !layout->zones.empty();
+}
+wf::geometry_t zone_workarea(wf::output_t *output, const output_stitch_zone_t& zone, bool reserved)
+{
+ auto area = zone_area(output, zone);
+ return inset_by_zone_gap(reserved ? reserve(area, output, zone.name) : area, zone.name);
+}
+wf::geometry_t plain_workarea(wf::output_t *output, bool reserved)
+{
+ auto area = output->workarea->get_workarea();
+ if (combined(output)) return area;
+ auto name = output->to_string();
+ return inset_by_zone_gap(reserved ? reserve(area, output, name) : area, name);
+}
+}
+
+wf::geometry_t physical_zone_workarea(wf::output_t *output, const output_stitch_zone_t& zone)
+{
+ return zone_workarea(output, zone, true);
+}
+wf::geometry_t unzoned_workarea(wf::output_t *output)
+{
+ return plain_workarea(output, true);
}
wf::geometry_t window_scope_workarea(wayfire_toplevel_view view)
{
- if (auto zone = window_scope_zone(view)) return physical_zone_workarea(view->get_output(), *zone);
- return unzoned_workarea(view->get_output());
+ const bool reserved = !view->has_data<monitor_reservation_exempt_t>();
+ if (auto zone = window_scope_zone(view)) return zone_workarea(view->get_output(), *zone, reserved);
+ return plain_workarea(view->get_output(), reserved);
+}
+
+wf::geometry_t monitor_area(wf::output_t *output, const std::string& monitor)
+{
+ if (!output) return {0, 0, 0, 0};
+ if (auto layout = output->get_data<output_stitch_layout_t>(); layout && !layout->zones.empty())
+ {
+ for (auto& zone : layout->zones) if (zone.name == monitor) return zone_area(output, zone);
+ return {0, 0, 0, 0};
+ }
+ return monitor == output->to_string() ? output->workarea->get_workarea() : wf::geometry_t{0, 0, 0, 0};
+}
+wf::geometry_t monitor_workarea(wf::output_t *output, const std::string& monitor)
+{
+ if (!output) return {0, 0, 0, 0};
+ if (auto layout = output->get_data<output_stitch_layout_t>(); layout && !layout->zones.empty())
+ {
+ for (auto& zone : layout->zones) if (zone.name == monitor) return zone_workarea(output, zone, true);
+ return {0, 0, 0, 0};
+ }
+ return monitor == output->to_string() ? plain_workarea(output, true) : wf::geometry_t{0, 0, 0, 0};
+}
+monitor_reservation_t get_monitor_reservation(wf::output_t *output, const std::string& monitor)
+{
+ return sum_reservations(output, monitor);
+}
+void set_monitor_reservation(wf::output_t *output, const std::string& monitor, const std::string& owner,
+ monitor_reservation_t reservation)
+{
+ if (!output) return;
+ auto before = sum_reservations(output, monitor);
+ auto old_workarea = monitor_workarea(output, monitor);
+ auto data = output->get_data_safe<monitor_reservations_t>();
+ if (reservation == monitor_reservation_t{})
+ {
+ if (auto it = data->monitors.find(monitor); it != data->monitors.end())
+ {
+ it->second.erase(owner);
+ if (it->second.empty()) data->monitors.erase(it);
+ }
+ } else
+ {
+ data->monitors[monitor][owner] = reservation;
+ }
+ if (sum_reservations(output, monitor) == before) return;
+ monitor_reservation_changed_signal ev{output, monitor, old_workarea};
+ output->emit(&ev);
}
wf::geometry_t window_scope_fullscreen(wayfire_toplevel_view view)
{
|