diff --git a/metadata/workarounds.xml b/metadata/workarounds.xml
index a7312b986..a4b14a9f5 100644
--- a/metadata/workarounds.xml
+++ b/metadata/workarounds.xml
@@ -105,5 +105,10 @@
0
500
+
diff --git a/plugins/common/move-drag-interface.cpp b/plugins/common/move-drag-interface.cpp
index daa035b4a..8945d8616 100644
--- a/plugins/common/move-drag-interface.cpp
+++ b/plugins/common/move-drag-interface.cpp
@@ -540,7 +540,7 @@ bool core_drag_t::is_view_held_in_place()
void core_drag_t::update_current_output(wf::point_t grab)
{
wf::pointf_t origin = {1.0 * grab.x, 1.0 * grab.y};
- auto output = wf::get_core().output_layout->get_output_coords_at(origin, origin);
+ auto output = wf::get_core().output_layout->find_closest_output(origin);
update_current_output(output);
}
diff --git a/plugins/single_plugins/alpha.cpp b/plugins/single_plugins/alpha.cpp
index 598d703b9..d83400a91 100644
--- a/plugins/single_plugins/alpha.cpp
+++ b/plugins/single_plugins/alpha.cpp
@@ -118,7 +118,7 @@ class wayfire_alpha : public wf::plugin_interface_t
wf::axis_callback axis_cb = [=] (wlr_pointer_axis_event *ev)
{
auto gc = wf::get_core().get_cursor_position();
- auto current_output = wf::get_core().output_layout->get_output_coords_at(gc, gc);
+ auto current_output = wf::get_core().output_layout->find_closest_output(gc);
if (!current_output || !current_output->can_activate_plugin(&grab_interface))
{
return false;
diff --git a/plugins/single_plugins/extra-gestures.cpp b/plugins/single_plugins/extra-gestures.cpp
index 5e0a8a350..89a15cfeb 100644
--- a/plugins/single_plugins/extra-gestures.cpp
+++ b/plugins/single_plugins/extra-gestures.cpp
@@ -49,7 +49,7 @@ class extra_gestures_plugin_t : public per_output_plugin_instance_t
auto center_touch_point = state.get_center().current;
wf::pointf_t center = {center_touch_point.x, center_touch_point.y};
- if (core.output_layout->get_output_at(center.x, center.y) != this->output)
+ if (core.output_layout->find_closest_output(center) != this->output)
{
return;
}
diff --git a/plugins/single_plugins/move.cpp b/plugins/single_plugins/move.cpp
index 469ecea70..9bb9e673d 100644
--- a/plugins/single_plugins/move.cpp
+++ b/plugins/single_plugins/move.cpp
@@ -314,7 +314,7 @@ class wayfire_move : public wf::per_output_plugin_instance_t,
bool initiate(wayfire_toplevel_view view, wf::point_t grab_position)
{
// First, make sure that the view is on the output the input is.
- auto target_output = wf::get_core().output_layout->get_output_at(grab_position.x, grab_position.y);
+ auto target_output = wf::get_core().output_layout->find_closest_output(wf::pointf_t{grab_position});
if (target_output && (view->get_output() != target_output))
{
auto parent = wf::find_topmost_parent(view);
diff --git a/src/api/wayfire/core.hpp b/src/api/wayfire/core.hpp
index 74da28cae..b6c29cbf9 100644
--- a/src/api/wayfire/core.hpp
+++ b/src/api/wayfire/core.hpp
@@ -173,7 +173,6 @@ class compositor_core_t : public wf::object_base_t, public signal::provider_t
wlr_export_dmabuf_manager_v1 *export_dmabuf;
wlr_server_decoration_manager *decorator_manager;
wlr_xdg_decoration_manager_v1 *xdg_decorator;
- wlr_xdg_output_manager_v1 *output_manager;
wlr_virtual_keyboard_manager_v1 *vkbd_manager;
wlr_virtual_pointer_manager_v1 *vptr_manager;
wlr_input_inhibit_manager *input_inhibit;
diff --git a/src/api/wayfire/nonstd/wlroots-full.hpp b/src/api/wayfire/nonstd/wlroots-full.hpp
index 719970eb3..2e1cfd833 100644
--- a/src/api/wayfire/nonstd/wlroots-full.hpp
+++ b/src/api/wayfire/nonstd/wlroots-full.hpp
@@ -123,7 +123,6 @@ extern "C"
#include
#endif
#include
-#include
#include
// Input
diff --git a/src/api/wayfire/output-layout.hpp b/src/api/wayfire/output-layout.hpp
index d3f5b8441..69af824f8 100644
--- a/src/api/wayfire/output-layout.hpp
+++ b/src/api/wayfire/output-layout.hpp
@@ -144,8 +144,12 @@ struct output_state_t
/** An output configuration is simply a list of each output with its state */
using output_configuration_t = std::map;
-/* output_layout_t is responsible for managing outputs and their attributes -
- * mode, scale, position, transform. */
+/**
+ * The output layout is a manager for all outputs in the compositor.
+ *
+ * It keeps track of all outputs reported by the wlroots backend (DRM connectors, wayland-backend nested
+ * outputs, etc.), as well as a virtual NOOP output for cases where no other outputs are present.
+ */
class output_layout_t : public wf::signal::provider_t
{
public:
@@ -158,48 +162,53 @@ class output_layout_t : public wf::signal::provider_t
wlr_output_layout *get_handle();
/**
- * @return the output at the given coordinates, or null if no such output
- */
- wf::output_t *get_output_at(int x, int y);
-
- /**
- * Get the output closest to the given origin and the closest coordinates
- * to origin which lie inside the output.
+ * Generate a list of the active outputs in the output layout.
+ * Here an active output is an output which is enabled (potentially in DPMS mode) and not mirrored.
+ * Such outputs have a corresponding logical wf::output_t object allocated to them.
*
- * @param origin The start coordinates
- * @param closest The closest point to origin inside the returned output
- * @return the output at the given coordinates
- */
- wf::output_t *get_output_coords_at(wf::pointf_t origin, wf::pointf_t& closest);
-
- /**
- * @return the number of the active outputs in the output layout
+ * In case that no outputs are present or enabled, the list will contain the NOOP output.
+ *
+ * @return a list of the active outputs in the output layout
*/
- size_t get_num_outputs();
+ std::vector get_outputs();
/**
- * @return a list of the active outputs in the output layout
+ * Find the active output which is closest to the given coordinates.
+ *
+ * @param origin The coordinates to find the closest output to
+ * @return The output closest to @origin
*/
- std::vector get_outputs();
+ wf::output_t *find_closest_output(wf::pointf_t origin);
/**
- * @return the "next" output in the layout. It is guaranteed that starting
- * with any output in the layout, and successively calling this function
- * will iterate over all outputs
+ * Find the active output which is closest to the given coordinates.
+ *
+ * @param origin The coordinates to find the closest output to
+ * @param closest Output parameter to store the coordinates of the closest point on the output found
+ * @return The output closest to @origin
*/
- wf::output_t *get_next_output(wf::output_t *output);
+ wf::output_t *find_closest_output(wf::pointf_t origin, wf::pointf_t& closest);
/**
* @return the output_t associated with the wlr_output, or null if the
* output isn't found
*/
wf::output_t *find_output(wlr_output *output);
+
+ /**
+ * @return the output_t with the given name, or null if not found
+ */
wf::output_t *find_output(std::string name);
/**
- * @return the current output configuration. This contains ALL outputs,
- * not just the ones in the actual layout (so disabled ones are included
- * as well)
+ * Get the current output configuration, consisting of every present output regardless of its state (i.e
+ * including disabled and mirrored outputs).
+ *
+ * Note that the output configuration includes only outputs reported by the wlroots backend. The NOOP
+ * output is never included here, even if it is the only present / enabled output.
+ * Thus, the output configuration may be empty even if get_outputs() returns a non-empty list.
+ *
+ * @return The current output configuration
*/
output_configuration_t get_current_configuration();
@@ -211,8 +220,8 @@ class output_layout_t : public wf::signal::provider_t
* outputs to their previous state.
*
* @param configuration The output configuration to be applied
- * @param test_only If true, this will only simulate applying
- * the configuration, without actually changing anything
+ * @param test_only If true, this will only simulate applying the configuration, without actually
+ * changing anything
*
* @return true on successful application, false otherwise
*/
diff --git a/src/api/wayfire/output.hpp b/src/api/wayfire/output.hpp
index afa603624..84ad634c2 100644
--- a/src/api/wayfire/output.hpp
+++ b/src/api/wayfire/output.hpp
@@ -100,6 +100,11 @@ class output_t : public wf::object_base_t, public wf::signal::provider_t
*/
wf::geometry_t get_layout_geometry() const;
+ /**
+ * Returns the current output scale.
+ */
+ float get_scale() const;
+
/**
* Moves the pointer so that it is inside the output
*
diff --git a/src/api/wayfire/plugin.hpp b/src/api/wayfire/plugin.hpp
index bcbe9f51f..631dc31c1 100644
--- a/src/api/wayfire/plugin.hpp
+++ b/src/api/wayfire/plugin.hpp
@@ -107,7 +107,7 @@ using wayfire_plugin_load_func = wf::plugin_interface_t * (*)();
/**
* The version is defined as macro as well, to allow conditional compilation.
*/
-#define WAYFIRE_API_ABI_VERSION_MACRO 2025'12'26
+#define WAYFIRE_API_ABI_VERSION_MACRO 2026'01'12
/**
* The version of Wayfire's API/ABI
diff --git a/src/api/wayfire/unstable/wlr-surface-node.hpp b/src/api/wayfire/unstable/wlr-surface-node.hpp
index f628781f8..c97a60692 100644
--- a/src/api/wayfire/unstable/wlr-surface-node.hpp
+++ b/src/api/wayfire/unstable/wlr-surface-node.hpp
@@ -19,6 +19,7 @@ struct surface_state_t
wlr_texture *texture; // The texture of the wlr_client_buffer
wf::region_t accumulated_damage;
+ wf::region_t opaque_region;
wf::dimensions_t size = {0, 0};
std::optional src_viewport;
wl_output_transform transform = WL_OUTPUT_TRANSFORM_NORMAL;
@@ -69,7 +70,7 @@ class wlr_surface_node_t : public node_t, public zero_copy_texturable_node_t
std::optional to_texture() const override;
wlr_surface *get_surface() const;
- void apply_state(surface_state_t&& state);
+ virtual void apply_state(surface_state_t&& state);
void apply_current_surface_state();
void send_frame_done(bool delay_until_vblank);
@@ -92,6 +93,8 @@ class wlr_surface_node_t : public node_t, public zero_copy_texturable_node_t
wf::wl_listener_wrapper on_surface_commit;
const bool autocommit;
+
+ protected:
surface_state_t current_state;
};
}
diff --git a/src/api/wayfire/unstable/xwl-toplevel-base.hpp b/src/api/wayfire/unstable/xwl-toplevel-base.hpp
index 716ae3af1..b259d060c 100644
--- a/src/api/wayfire/unstable/xwl-toplevel-base.hpp
+++ b/src/api/wayfire/unstable/xwl-toplevel-base.hpp
@@ -10,11 +10,17 @@
#include
#include
+#include
#include
namespace wf
{
#if WF_HAS_XWAYLAND
+namespace xw
+{
+class xwayland_surface_node_t;
+}
+
/**
* A base class for views which base on a wlr_xwayland surface.
* Contains the implementation of view_interface_t functions used in them.
@@ -50,7 +56,7 @@ class xwayland_view_base_t : public virtual wf::view_interface_t
void handle_title_changed(std::string new_title);
wf::wl_listener_wrapper on_destroy, on_set_title, on_set_app_id, on_ping_timeout;
- std::shared_ptr main_surface;
+ std::shared_ptr main_surface;
};
#endif
}
diff --git a/src/core/core-impl.hpp b/src/core/core-impl.hpp
index 016146b21..4627b6c72 100644
--- a/src/core/core-impl.hpp
+++ b/src/core/core-impl.hpp
@@ -8,6 +8,7 @@
#include "wayfire/scene.hpp"
#include "wayfire/util.hpp"
#include
+#include "src/core/xdg-output-management.hpp"
namespace wf
{
@@ -24,6 +25,7 @@ class compositor_core_impl_t : public compositor_core_t
std::unique_ptr input;
std::unique_ptr im_relay;
std::unique_ptr plugin_mgr;
+ std::unique_ptr xdg_output_manager;
/**
* Initialize the compositor core.
diff --git a/src/core/core.cpp b/src/core/core.cpp
index 74543c42f..caddd1ee8 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -149,9 +149,9 @@ void wf::compositor_core_impl_t::init()
protocols.image_copy_capture = wlr_ext_image_copy_capture_manager_v1_create(display, 1);
protocols.image_capture_source = wlr_ext_output_image_capture_source_manager_v1_create(display, 1);
protocols.gamma_v1 = wlr_gamma_control_manager_v1_create(display);
- protocols.export_dmabuf = wlr_export_dmabuf_manager_v1_create(display);
- protocols.output_manager = wlr_xdg_output_manager_v1_create(display,
- output_layout->get_handle());
+ protocols.export_dmabuf = wlr_export_dmabuf_manager_v1_create(display);
+ xdg_output_manager = std::make_unique(display,
+ output_layout.get());
protocols.drm_v1 = wlr_drm_lease_v1_manager_create(display, backend);
drm_lease_request.set_callback([&] (void *data)
{
@@ -294,8 +294,7 @@ void wf::compositor_core_impl_t::post_init()
this->state = compositor_state_t::RUNNING;
// Move pointer to the middle of the leftmost, topmost output
- wf::pointf_t p;
- wf::output_t *wo = wf::get_core().output_layout->get_output_coords_at({FLT_MIN, FLT_MIN}, p);
+ wf::output_t *wo = output_layout->find_closest_output({FLT_MIN, FLT_MIN});
// Output might be noop but guaranteed to not be null
wo->ensure_pointer(true);
seat->focus_output(wo);
diff --git a/src/core/output-layout.cpp b/src/core/output-layout.cpp
index f25b1e9db..93e70afe7 100644
--- a/src/core/output-layout.cpp
+++ b/src/core/output-layout.cpp
@@ -635,8 +635,18 @@ struct output_layout_output_t
bool shutdown = is_shutting_down();
if ((get_core().seat->get_active_output() == wo) && !shutdown)
{
- get_core().seat->focus_output(
- get_core().output_layout->get_next_output(wo));
+ auto outputs = get_core().output_layout->get_outputs();
+ wf::dassert(!outputs.empty(),
+ "There should be at least one output left if we're not shutting down");
+
+ auto it = std::find(outputs.begin(), outputs.end(), wo);
+ if ((it == outputs.end()) || (std::next(it) == outputs.end()))
+ {
+ get_core().seat->focus_output(outputs[0]);
+ } else
+ {
+ get_core().seat->focus_output(*(std::next(it)));
+ }
} else if (shutdown)
{
get_core().seat->focus_output(nullptr);
@@ -1798,11 +1808,6 @@ class output_layout_t::impl
return output_layout;
}
- size_t get_num_outputs()
- {
- return get_outputs().size();
- }
-
wf::output_t *find_output(wlr_output *output)
{
if (outputs.count(output))
@@ -1855,28 +1860,13 @@ class output_layout_t::impl
return result;
}
- wf::output_t *get_next_output(wf::output_t *output)
- {
- auto os = get_outputs();
-
- auto it = std::find(os.begin(), os.end(), output);
- if ((it == os.end()) || (std::next(it) == os.end()))
- {
- return os[0];
- } else
- {
- return *(++it);
- }
- }
-
- wf::output_t *get_output_coords_at(const wf::pointf_t& origin,
+ wf::output_t *find_closest_output(const wf::pointf_t& origin,
wf::pointf_t& closest)
{
wlr_output_layout_closest_point(output_layout, NULL,
origin.x, origin.y, &closest.x, &closest.y);
- auto handle =
- wlr_output_layout_output_at(output_layout, closest.x, closest.y);
+ auto handle = wlr_output_layout_output_at(output_layout, closest.x, closest.y);
assert(handle || is_shutting_down());
if (!handle)
{
@@ -1892,13 +1882,6 @@ class output_layout_t::impl
}
}
- wf::output_t *get_output_at(int x, int y)
- {
- wf::pointf_t dummy;
-
- return get_output_coords_at({1.0 * x, 1.0 * y}, dummy);
- }
-
bool apply_configuration(const output_configuration_t& configuration,
bool test_only)
{
@@ -1922,20 +1905,16 @@ wlr_output_layout*output_layout_t::get_handle()
return pimpl->get_handle();
}
-wf::output_t*output_layout_t::get_output_at(int x, int y)
+wf::output_t*output_layout_t::find_closest_output(wf::pointf_t point)
{
- return pimpl->get_output_at(x, y);
+ wf::pointf_t dummy;
+ return find_closest_output(point, dummy);
}
-wf::output_t*output_layout_t::get_output_coords_at(wf::pointf_t origin,
+wf::output_t*output_layout_t::find_closest_output(wf::pointf_t origin,
wf::pointf_t& closest)
{
- return pimpl->get_output_coords_at(origin, closest);
-}
-
-size_t output_layout_t::get_num_outputs()
-{
- return pimpl->get_num_outputs();
+ return pimpl->find_closest_output(origin, closest);
}
std::vector output_layout_t::get_outputs()
@@ -1943,11 +1922,6 @@ std::vector output_layout_t::get_outputs()
return pimpl->get_outputs();
}
-wf::output_t*output_layout_t::get_next_output(wf::output_t *output)
-{
- return pimpl->get_next_output(output);
-}
-
wf::output_t*output_layout_t::find_output(wlr_output *output)
{
return pimpl->find_output(output);
diff --git a/src/core/seat/hotspot-manager.cpp b/src/core/seat/hotspot-manager.cpp
index 353a10c91..6b8d8204b 100644
--- a/src/core/seat/hotspot-manager.cpp
+++ b/src/core/seat/hotspot-manager.cpp
@@ -11,7 +11,7 @@ void wf::hotspot_instance_t::process_input_motion(wf::pointf_t gc)
this->armed = true;
};
- auto target = wf::get_core().output_layout->get_output_coords_at(gc, gc);
+ auto target = wf::get_core().output_layout->find_closest_output(gc, gc);
if (target != last_output)
{
reset_hotspot();
diff --git a/src/core/seat/pointer.cpp b/src/core/seat/pointer.cpp
index cfd786c67..75a5a47fa 100644
--- a/src/core/seat/pointer.cpp
+++ b/src/core/seat/pointer.cpp
@@ -221,7 +221,7 @@ void wf::pointer_t::handle_pointer_button(wlr_pointer_button_event *ev,
/* Focus only the first click, since then we also start an implicit
* grab, and we don't want to suddenly change the output */
auto gc = seat->priv->cursor->get_cursor_position();
- auto output = wf::get_core().output_layout->get_output_at(gc.x, gc.y);
+ auto output = wf::get_core().output_layout->find_closest_output(gc);
seat->focus_output(output);
}
diff --git a/src/core/seat/tablet.cpp b/src/core/seat/tablet.cpp
index c603f604a..fc5ed20c5 100644
--- a/src/core/seat/tablet.cpp
+++ b/src/core/seat/tablet.cpp
@@ -321,8 +321,7 @@ void wf::tablet_t::handle_tip(wlr_tablet_tool_tip_event *ev,
if (ev->state == WLR_TABLET_TOOL_TIP_DOWN)
{
auto gc = seat->priv->cursor->get_cursor_position();
- auto output =
- wf::get_core().output_layout->get_output_at(gc.x, gc.y);
+ auto output = wf::get_core().output_layout->find_closest_output(gc);
wf::get_core().seat->focus_output(output);
handled_in_binding |= wf::get_core().bindings->handle_button(
diff --git a/src/core/seat/touch.cpp b/src/core/seat/touch.cpp
index bc2378547..8c4394f8d 100644
--- a/src/core/seat/touch.cpp
+++ b/src/core/seat/touch.cpp
@@ -44,7 +44,7 @@ wf::touch_interface_t::touch_interface_t(wlr_cursor *cursor, wlr_seat *seat,
wlr_cursor_absolute_to_layout_coords(cursor, &ev->touch->base, ev->x, ev->y, &lx, &ly);
wf::pointf_t point;
- wf::get_core().output_layout->get_output_coords_at({lx, ly}, point);
+ wf::get_core().output_layout->find_closest_output({lx, ly}, point);
handle_touch_down(ev->touch_id, ev->time_msec, point, mode);
}
@@ -78,7 +78,7 @@ wf::touch_interface_t::touch_interface_t(wlr_cursor *cursor, wlr_seat *seat,
ev->x, ev->y, &lx, &ly);
wf::pointf_t point;
- wf::get_core().output_layout->get_output_coords_at({lx, ly}, point);
+ wf::get_core().output_layout->find_closest_output({lx, ly}, point);
handle_touch_motion(ev->touch_id, ev->time_msec, point, true, mode);
}
@@ -220,8 +220,7 @@ void wf::touch_interface_t::handle_touch_down(int32_t id, uint32_t time,
if (id == 0)
{
- wf::get_core().seat->focus_output(
- wf::get_core().output_layout->get_output_at(point.x, point.y));
+ wf::get_core().seat->focus_output(wf::get_core().output_layout->find_closest_output(point));
}
// NB. We first update the focus, and then update the gesture,
diff --git a/src/core/xdg-output-management.cpp b/src/core/xdg-output-management.cpp
new file mode 100644
index 000000000..bf5a303c8
--- /dev/null
+++ b/src/core/xdg-output-management.cpp
@@ -0,0 +1,236 @@
+// XDG Output Management Wayland protocol
+// Inspired by the wlroots implementation
+#include "xdg-output-management.hpp"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include "view/view-impl.hpp"
+#include "wayfire/debug.hpp"
+#include "xdg-output-unstable-v1-protocol.h"
+
+namespace wf
+{
+#define OUTPUT_MANAGER_VERSION 3
+#define OUTPUT_DONE_DEPRECATED_SINCE_VERSION 3
+#define OUTPUT_DESCRIPTION_MUTABLE_SINCE_VERSION 3
+
+static void output_handle_destroy(wl_client* /* client */, wl_resource *resource)
+{
+ wl_resource_destroy(resource);
+}
+
+constexpr static const struct zxdg_output_v1_interface output_implementation = {
+ .destroy = output_handle_destroy,
+};
+
+class xdg_output_v1_resource
+{
+ std::optional last_sent_geometry;
+ wf::wl_listener_wrapper on_description_changed;
+
+ public:
+ wl_resource *xdg_output;
+
+ xdg_output_v1_resource(wl_resource *resource, wlr_output *output)
+ {
+ uint32_t proto_version = wl_resource_get_version(resource);
+ this->xdg_output = resource;
+
+ if (proto_version >= ZXDG_OUTPUT_V1_NAME_SINCE_VERSION)
+ {
+ zxdg_output_v1_send_name(xdg_output, output->name);
+ }
+
+ on_description_changed.set_callback([=] (void*)
+ {
+ if ((proto_version >= ZXDG_OUTPUT_V1_DESCRIPTION_SINCE_VERSION) && (output->description != NULL))
+ {
+ zxdg_output_v1_send_description(xdg_output, output->description);
+ }
+ });
+
+ on_description_changed.emit(NULL);
+ if (proto_version >= OUTPUT_DESCRIPTION_MUTABLE_SINCE_VERSION)
+ {
+ on_description_changed.connect(&output->events.description);
+ }
+ }
+
+ bool is_xwayland() const
+ {
+ return wl_resource_get_client(xdg_output) == wf::xwayland_get_client();
+ }
+
+ bool resend_details(wf::geometry_t geometry)
+ {
+ if (last_sent_geometry == geometry)
+ {
+ return false;
+ }
+
+ last_sent_geometry = geometry;
+ zxdg_output_v1_send_logical_position(xdg_output, geometry.x, geometry.y);
+ zxdg_output_v1_send_logical_size(xdg_output, geometry.width, geometry.height);
+ if (wl_resource_get_version(xdg_output) < OUTPUT_DONE_DEPRECATED_SINCE_VERSION)
+ {
+ zxdg_output_v1_send_done(xdg_output);
+ }
+
+ return true;
+ }
+};
+
+constexpr const struct zxdg_output_manager_v1_interface xdg_output_manager_v1::output_manager_implementation =
+{
+ .destroy = xdg_output_manager_v1::generic_handle_destroy,
+ .get_xdg_output = xdg_output_manager_v1::handle_get_xdg_output,
+};
+
+xdg_output_manager_v1::xdg_output_manager_v1(wl_display *display, wf::output_layout_t *layout)
+{
+ this->global = wl_global_create(display, &zxdg_output_manager_v1_interface,
+ OUTPUT_MANAGER_VERSION, this, xdg_output_manager_v1::output_manager_bind);
+
+ this->layout_change.set_callback([this] (wf::output_layout_configuration_changed_signal* /* signal */)
+ {
+ this->update_outputs();
+ });
+ layout->connect(&this->layout_change);
+}
+
+xdg_output_manager_v1::~xdg_output_manager_v1() = default;
+
+void xdg_output_manager_v1::update_outputs()
+{
+ auto& ol = wf::get_core().output_layout;
+ auto config = ol->get_current_configuration();
+
+ auto output_visible_for_clients = [&] (wlr_output *output)
+ {
+ // NULL output is not part of the configuration state, so we need a separate check
+ if ((std::string(nonull(output->name)) == "NOOP-1") && output->enabled)
+ {
+ return true;
+ }
+
+ return config.count(output) && (config[output].source & OUTPUT_IMAGE_SOURCE_SELF);
+ };
+
+ const auto& update_output = [&] (wlr_output *output, wf::geometry_t geometry,
+ wf::geometry_t xwayland_geometry)
+ {
+ bool changed = false;
+ for (auto& resource : output_resources[output])
+ {
+ wf::geometry_t to_send = resource->is_xwayland() ? xwayland_geometry : geometry;
+ changed |= resource->resend_details(to_send);
+ }
+
+ if (changed)
+ {
+ wlr_output_schedule_done(output);
+ }
+ };
+
+ static wf::option_wrapper_t force_xwayland_scaling{"workarounds/force_xwayland_scaling"};
+
+ int xwayland_location_x = 0;
+ auto it = output_resources.begin();
+ while (it != output_resources.end())
+ {
+ if (!output_visible_for_clients(it->first))
+ {
+ it = output_resources.erase(it);
+ } else
+ {
+ auto wo = ol->find_output(it->first);
+ auto geometry = wo->get_layout_geometry();
+ if (force_xwayland_scaling)
+ {
+ int width, height;
+ wlr_output_transformed_resolution(it->first, &width, &height);
+ wf::geometry_t xwayland_geometry = {xwayland_location_x, 0, width, height};
+ update_output(it->first, geometry, xwayland_geometry);
+ xwayland_location_x += width;
+ wo->get_data_safe()->geometry = xwayland_geometry;
+ } else
+ {
+ update_output(it->first, geometry, geometry);
+ wo->get_data_safe()->geometry = geometry;
+ }
+
+ ++it;
+ }
+ }
+}
+
+void xdg_output_manager_v1::output_manager_bind(wl_client *wl_client, void *data,
+ uint32_t version, uint32_t id)
+{
+ xdg_output_manager_v1 *self = static_cast(data);
+ wl_resource *resource = wl_resource_create(wl_client, &zxdg_output_manager_v1_interface, version, id);
+ if (resource == NULL)
+ {
+ wl_client_post_no_memory(wl_client);
+ return;
+ }
+
+ wl_resource_set_implementation(resource, &xdg_output_manager_v1::output_manager_implementation, self,
+ NULL);
+}
+
+void xdg_output_manager_v1::generic_handle_destroy(wl_client* /* client */, wl_resource *resource)
+{
+ wl_resource_destroy(resource);
+}
+
+void xdg_output_manager_v1::handle_output_resource_destroy(wl_resource *resource)
+{
+ auto self = static_cast(wl_resource_get_user_data(resource));
+ for (auto& [output, resources] : self->output_resources)
+ {
+ resources.erase(std::remove_if(resources.begin(), resources.end(),
+ [resource] (const std::unique_ptr& res)
+ {
+ return res->xdg_output == resource;
+ }), resources.end());
+ }
+}
+
+void xdg_output_manager_v1::handle_get_xdg_output(wl_client *client, wl_resource *resource,
+ uint32_t id, wl_resource *output_resource)
+{
+ auto self = static_cast(wl_resource_get_user_data(resource));
+ wlr_output *output = wlr_output_from_resource(output_resource);
+ uint32_t proto_version = wl_resource_get_version(resource);
+ if (!output)
+ {
+ auto inert = wl_resource_create(client, &zxdg_output_v1_interface, proto_version, id);
+ wl_resource_set_implementation(inert, &output_implementation, NULL, NULL);
+ return;
+ }
+
+ wl_resource *xdg_output_resource = wl_resource_create(client,
+ &zxdg_output_v1_interface, proto_version, id);
+
+ if (!xdg_output_resource)
+ {
+ wl_client_post_no_memory(client);
+ return;
+ }
+
+ wl_resource_set_implementation(xdg_output_resource, &output_implementation,
+ self, xdg_output_manager_v1::handle_output_resource_destroy);
+
+ auto xdg_output = std::make_unique(xdg_output_resource, output);
+ self->output_resources[output].push_back(std::move(xdg_output));
+ self->update_outputs();
+}
+}
diff --git a/src/core/xdg-output-management.hpp b/src/core/xdg-output-management.hpp
new file mode 100644
index 000000000..10d218b6c
--- /dev/null
+++ b/src/core/xdg-output-management.hpp
@@ -0,0 +1,39 @@
+#pragma once
+
+#include
+#include