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 +#include +#include "wayfire/object.hpp" +#include "xdg-output-unstable-v1-protocol.h" +#include + +namespace wf +{ +class xdg_output_v1_resource; +class xdg_output_xwayland_geometry : public wf::custom_data_t +{ + public: + std::optional geometry; +}; + +class xdg_output_manager_v1 +{ + public: + xdg_output_manager_v1(wl_display *display, wf::output_layout_t *layout); + ~xdg_output_manager_v1(); + + private: + wl_global *global; + std::map>> output_resources; + wf::signal::connection_t layout_change; + + void update_outputs(); + + static void output_manager_bind(wl_client *wl_client, void *data, uint32_t version, uint32_t id); + static void generic_handle_destroy(wl_client *client, wl_resource *resource); + static void handle_output_resource_destroy(wl_resource *resource); + static void handle_get_xdg_output(wl_client *client, wl_resource *resource, uint32_t id, + wl_resource *output_resource); + static const struct zxdg_output_manager_v1_interface output_manager_implementation; +}; +} // namespace wf diff --git a/src/meson.build b/src/meson.build index 7d374ea0f..29c53fce6 100644 --- a/src/meson.build +++ b/src/meson.build @@ -17,6 +17,7 @@ wayfire_sources = ['geometry.cpp', 'core/img.cpp', 'core/wm.cpp', 'core/view-access-interface.cpp', + 'core/xdg-output-management.cpp', 'core/txn/transaction.cpp', 'core/txn/transaction-manager.cpp', @@ -50,6 +51,7 @@ wayfire_sources = ['geometry.cpp', 'view/xwayland/xwayland-view-base.cpp', 'view/xwayland/xwayland-toplevel.cpp', 'view/xwayland/xwayland-helpers.cpp', + 'view/xwayland/xwayland-surface.cpp', 'view/layer-shell/layer-shell.cpp', 'view/layer-shell/layer-shell-node.cpp', 'view/view-3d.cpp', diff --git a/src/output/output.cpp b/src/output/output.cpp index 45ec77909..ec97c8945 100644 --- a/src/output/output.cpp +++ b/src/output/output.cpp @@ -160,6 +160,11 @@ wf::geometry_t wf::output_t::get_layout_geometry() const } } +float wf::output_t::get_scale() const +{ + return handle->scale; +} + void wf::output_t::ensure_pointer(bool center) const { auto ptr = wf::get_core().get_cursor_position(); @@ -402,7 +407,7 @@ void output_impl_t::add_activator( if (act.source == activator_source_t::HOTSPOT) { auto pos = wf::get_core().get_cursor_position(); - auto wo = wf::get_core().output_layout->get_output_at(pos.x, pos.y); + auto wo = wf::get_core().output_layout->find_closest_output(pos); if (this != wo) { return false; diff --git a/src/view/view-impl.hpp b/src/view/view-impl.hpp index f57543178..ce3b32d0c 100644 --- a/src/view/view-impl.hpp +++ b/src/view/view-impl.hpp @@ -76,6 +76,7 @@ void xwayland_update_default_cursor(); /* Ensure that the given surface is on top of the Xwayland stack order. */ void xwayland_bring_to_front(wlr_surface *surface); int xwayland_get_pid(); +wl_client *xwayland_get_client(); void init_desktop_apis(); void fini_desktop_apis(); diff --git a/src/view/wlr-surface-node.cpp b/src/view/wlr-surface-node.cpp index 91d8ab38d..6f81c5cfc 100644 --- a/src/view/wlr-surface-node.cpp +++ b/src/view/wlr-surface-node.cpp @@ -1,5 +1,4 @@ #include "wayfire/unstable/wlr-surface-node.hpp" -#include "pixman.h" #include "wayfire/geometry.hpp" #include "wayfire/render-manager.hpp" #include "wayfire/scene-render.hpp" @@ -32,6 +31,7 @@ wf::scene::surface_state_t& wf::scene::surface_state_t::operator =(surface_state current_buffer = other.current_buffer; texture = other.texture; accumulated_damage = other.accumulated_damage; + opaque_region = other.opaque_region; seq = other.seq; size = other.size; src_viewport = other.src_viewport; @@ -40,6 +40,7 @@ wf::scene::surface_state_t& wf::scene::surface_state_t::operator =(surface_state other.current_buffer = NULL; other.texture = NULL; other.accumulated_damage.clear(); + other.opaque_region.clear(); other.src_viewport.reset(); other.seq.reset(); return *this; @@ -86,6 +87,7 @@ void wf::scene::surface_state_t::merge_state(wlr_surface *surface) wf::region_t current_damage; wlr_surface_get_effective_damage(surface, current_damage.to_pixman()); this->accumulated_damage |= current_damage; + this->opaque_region = wf::region_t{&surface->current.opaque}; } wf::scene::surface_state_t::~surface_state_t() @@ -312,8 +314,7 @@ class wf::scene::wlr_surface_node_t::wlr_surface_render_instance_t : public rend if (self->surface) { - pixman_region32_subtract(damage.to_pixman(), damage.to_pixman(), - &self->surface->opaque_region); + damage ^= self->current_state.opaque_region; } } } @@ -402,8 +403,7 @@ class wf::scene::wlr_surface_node_t::wlr_surface_render_instance_t : public rend output->connect(&on_frame_done); if (use_opaque_optimizations && self->surface) { - pixman_region32_subtract(visible.to_pixman(), visible.to_pixman(), - &self->surface->opaque_region); + visible ^= self->current_state.opaque_region; } } } diff --git a/src/view/xwayland.cpp b/src/view/xwayland.cpp index 38e9f2225..87d432631 100644 --- a/src/view/xwayland.cpp +++ b/src/view/xwayland.cpp @@ -336,3 +336,14 @@ int wf::xwayland_get_pid() return -1; #endif } + +wl_client*wf::xwayland_get_client() +{ +#if WF_HAS_XWAYLAND + + return (xwayland_handle && xwayland_handle->server) ? xwayland_handle->server->client : NULL; +#else + + return nullptr; +#endif +} diff --git a/src/view/xwayland/xwayland-helpers.cpp b/src/view/xwayland/xwayland-helpers.cpp index dd3f16f1c..1e4ed37fe 100644 --- a/src/view/xwayland/xwayland-helpers.cpp +++ b/src/view/xwayland/xwayland-helpers.cpp @@ -1,4 +1,5 @@ #include "xwayland-helpers.hpp" +#include "core/xdg-output-management.hpp" #if WF_HAS_XWAYLAND @@ -50,4 +51,58 @@ bool wf::xw::has_type(wlr_xwayland_surface *xw, xcb_atom_t type) return false; } +wf::output_t*wf::xw::find_xwayland_surface_output(wlr_xwayland_surface *xw) +{ + auto& ol = wf::get_core().output_layout; + double cx = xw->x + xw->width / 2.0; + double cy = xw->y + xw->height / 2.0; + wf::output_t *closest = ol->get_outputs().empty() ? nullptr : ol->get_outputs().front(); + double closest_dist = std::numeric_limits::max(); + for (auto & wo : ol->get_outputs()) + { + auto data = wo->get_data_safe(); + if (!data->geometry.has_value()) + { + continue; + } + + double dx; + double dy; + wlr_box_closest_point(&data->geometry.value(), cx, cy, &dx, &dy); + const double dist = (dx - cx) * (dx - cx) + (dy - cy) * (dy - cy); + if (dist < closest_dist) + { + closest_dist = dist; + closest = wo; + } + } + + return closest; +} + +wf::geometry_t wf::xw::calculate_wayfire_geometry(wf::output_t *ref_output, wf::geometry_t geometry) +{ + if (!ref_output) + { + return geometry; + } + + auto data = ref_output->get_data_safe(); + if (!data->geometry.has_value()) + { + LOGW("Xwayland geometry not set for output ", ref_output->to_string(), + ", returning original geometry"); + return geometry; + } + + geometry = geometry - wf::origin(data->geometry.value()); + static wf::option_wrapper_t force_xwayland_scaling{"workarounds/force_xwayland_scaling"}; + if (force_xwayland_scaling) + { + geometry = geometry * (1.0 / ref_output->get_scale()); + } + + return geometry; +} + #endif diff --git a/src/view/xwayland/xwayland-helpers.hpp b/src/view/xwayland/xwayland-helpers.hpp index 5d2001472..94c9ac45c 100644 --- a/src/view/xwayland/xwayland-helpers.hpp +++ b/src/view/xwayland/xwayland-helpers.hpp @@ -1,6 +1,7 @@ #pragma once #include "config.h" +#include "wayfire/output.hpp" #include #include @@ -29,6 +30,13 @@ extern xcb_atom_t _NET_WM_WINDOW_TYPE_DND; std::optional load_atom(xcb_connection_t *connection, const std::string& name); bool load_basic_atoms(const char *server_name); bool has_type(wlr_xwayland_surface *xw, xcb_atom_t type); + +wf::output_t *find_xwayland_surface_output(wlr_xwayland_surface *xw); + +/** + * Calculate the geometry from the Xwayland client to be used in Wayfire. + */ +wf::geometry_t calculate_wayfire_geometry(wf::output_t *ref_output, wf::geometry_t geometry); } } diff --git a/src/view/xwayland/xwayland-surface.cpp b/src/view/xwayland/xwayland-surface.cpp new file mode 100644 index 000000000..6edf928d1 --- /dev/null +++ b/src/view/xwayland/xwayland-surface.cpp @@ -0,0 +1,60 @@ +#include "xwayland-surface.hpp" + +void wf::xw::xwayland_surface_node_t::set_scale(float scale) +{ + if (scale != this->current_scale) + { + wf::scene::damage_node(this, this->get_bounding_box()); + const float rescale = this->current_scale / scale; + + this->current_state.size.width = int(this->current_state.size.width * rescale); + this->current_state.size.height = int(this->current_state.size.height * rescale); + this->current_state.accumulated_damage *= rescale; + this->current_state.opaque_region *= rescale; + if (this->current_state.src_viewport.has_value()) + { + wlr_fbox& box = this->current_state.src_viewport.value(); + box = box * rescale; + } + + this->current_scale = scale; + wf::scene::damage_node(this, this->get_bounding_box()); + } +} + +std::string wf::xw::xwayland_surface_node_t::stringify() const +{ + std::ostringstream name; + name << "xwayland(scale=" << current_scale << ") " << wlr_surface_node_t::stringify(); + return name.str(); +} + +wf::pointf_t wf::xw::xwayland_surface_node_t::to_local(const wf::pointf_t& point) +{ + return {point.x * current_scale, point.y * current_scale}; +} + +wf::pointf_t wf::xw::xwayland_surface_node_t::to_global(const wf::pointf_t& point) +{ + return {point.x / current_scale, point.y / current_scale}; +} + +void wf::xw::xwayland_surface_node_t::apply_state(scene::surface_state_t&& state) +{ + state.size = wf::dimensions_t{ + int(state.size.width / current_scale), + int(state.size.height / current_scale) + }; + state.accumulated_damage *= (1.0 / current_scale); + state.opaque_region *= (1.0 / current_scale); + if (state.src_viewport.has_value()) + { + wlr_fbox& box = state.src_viewport.value(); + box.x /= current_scale; + box.y /= current_scale; + box.width /= current_scale; + box.height /= current_scale; + } + + wlr_surface_node_t::apply_state(std::move(state)); +} diff --git a/src/view/xwayland/xwayland-surface.hpp b/src/view/xwayland/xwayland-surface.hpp new file mode 100644 index 000000000..55e558074 --- /dev/null +++ b/src/view/xwayland/xwayland-surface.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include + +namespace wf +{ +namespace xw +{ +/** + * A custom surface node class for Xwayland surfaces. + * It is almost identical to wlr_surface_node_t, but it applies scaling to the coordinates to accomodate + * for the fake Xwayland scaling that workarounds/force_xwayland_scaling introduces. + */ +class xwayland_surface_node_t : public wf::scene::wlr_surface_node_t +{ + public: + using wlr_surface_node_t::wlr_surface_node_t; + + std::string stringify() const override; + wf::pointf_t to_local(const wf::pointf_t& point) override; + wf::pointf_t to_global(const wf::pointf_t& point) override; + void apply_state(scene::surface_state_t&& state) override; + + void set_scale(float scale); + + private: + float current_scale = 1.0f; +}; +} +} diff --git a/src/view/xwayland/xwayland-toplevel-view.hpp b/src/view/xwayland/xwayland-toplevel-view.hpp index e924736cc..1817e64b5 100644 --- a/src/view/xwayland/xwayland-toplevel-view.hpp +++ b/src/view/xwayland/xwayland-toplevel-view.hpp @@ -2,6 +2,7 @@ #include "config.h" #include "view/view-impl.hpp" +#include "view/xwayland/xwayland-surface.hpp" #include "wayfire/core.hpp" #include "wayfire/debug.hpp" #include "wayfire/geometry.hpp" @@ -34,18 +35,18 @@ class wayfire_xwayland_view : public wf::toplevel_view_interface_t, public wayfi wf::signal::connection_t output_geometry_changed = [=] (wf::output_configuration_changed_signal *ev) { - toplevel->set_output_offset(wf::origin(ev->output->get_layout_geometry())); + toplevel->update_output(ev->output); }; void handle_client_configure(wlr_xwayland_surface_configure_event *ev) override { - wf::point_t output_origin = {0, 0}; + wf::geometry_t configure_geometry = wlr_box{ev->x, ev->y, ev->width, ev->height}; if (get_output()) { - output_origin = wf::origin(get_output()->get_layout_geometry()); + configure_geometry = wf::xw::calculate_wayfire_geometry(get_output(), configure_geometry); } - LOGC(XWL, "Client configure ", self(), " at ", ev->x, ",", ev->y, " ", ev->width, "x", ev->height); + LOGC(XWL, "Client configures ", self(), " to ", configure_geometry); if (!is_mapped()) { /* If the view is not mapped yet, let it be configured as it @@ -53,18 +54,16 @@ class wayfire_xwayland_view : public wf::toplevel_view_interface_t, public wayfi wlr_xwayland_surface_configure(xw, ev->x, ev->y, ev->width, ev->height); if ((ev->mask & XCB_CONFIG_WINDOW_X) && (ev->mask & XCB_CONFIG_WINDOW_Y)) { - int sx = ev->x - output_origin.x; - int sy = ev->y - output_origin.y; - this->set_property("startup-x", sx); - this->set_property("startup-y", sy); - toplevel->pending().geometry.x = sx; - toplevel->pending().geometry.y = sy; + this->set_property("startup-x", configure_geometry.x); + this->set_property("startup-y", configure_geometry.y); + toplevel->pending().geometry.x = configure_geometry.x; + toplevel->pending().geometry.y = configure_geometry.y; } return; } - configure_request(wlr_box{ev->x, ev->y, ev->width, ev->height}); + configure_request(configure_geometry); } void update_decorated() @@ -74,47 +73,45 @@ class wayfire_xwayland_view : public wf::toplevel_view_interface_t, public wayfi this->set_decoration_mode(xw->decorations & csd_flags); } + /** + * Handle a configure request for a mapped xwayland view. + * + * @param configure_geometry The desired geometry from the client request, in output-local coordinates. + */ void configure_request(wf::geometry_t configure_geometry) { configure_geometry = wf::expand_geometry_by_margins(configure_geometry, toplevel->pending().margins); - wf::output_t *o = get_output(); - if (!o) - { - set_geometry(configure_geometry); - return; - } - - auto og = o->get_layout_geometry(); // If client is fullscreen or tiled, do not allow it to reposition itself. // Otherwise, we will make sure it remains on its original workspace. if (pending_fullscreen() || pending_tiled_edges()) { - configure_geometry.x = get_pending_geometry().x + og.x; - configure_geometry.y = get_pending_geometry().y + og.y; + configure_geometry.x = get_pending_geometry().x; + configure_geometry.y = get_pending_geometry().y; set_geometry(configure_geometry); return; } - // Translate to output-local coordinates - configure_geometry.x -= og.x; - configure_geometry.y -= og.y; + if (get_output()) + { + auto workarea = get_output()->workarea->get_workarea(); + auto og = get_output()->get_screen_size(); - auto workarea = o->workarea->get_workarea(); - wayfire_toplevel_view main_view = wf::find_topmost_parent(wayfire_toplevel_view{this}); + wayfire_toplevel_view main_view = wf::find_topmost_parent(wayfire_toplevel_view{this}); - // View workspace relative to current workspace - if (main_view->get_wset()) - { - auto view_ws = main_view->get_wset()->get_view_main_workspace(main_view); - workarea.x += og.width * view_ws.x; - workarea.y += og.height * view_ws.y; - } + // View workspace relative to current workspace + if (main_view->get_wset()) + { + auto view_ws = main_view->get_wset()->get_view_main_workspace(main_view); + workarea.x += og.width * view_ws.x; + workarea.y += og.height * view_ws.y; + } - // Ensure view remains visible - if (!(configure_geometry & workarea)) - { - configure_geometry = wf::clamp(configure_geometry, workarea); + // Ensure view remains visible + if (!(configure_geometry & workarea)) + { + configure_geometry = wf::clamp(configure_geometry, workarea); + } } set_geometry(configure_geometry); @@ -282,7 +279,7 @@ class wayfire_xwayland_view : public wf::toplevel_view_interface_t, public wayfi }; /* Make sure geometry is properly visible on the view output */ - save_geometry = save_geometry - wf::origin(get_output()->get_layout_geometry()); + save_geometry = wf::xw::calculate_wayfire_geometry(get_output(), save_geometry); save_geometry = wf::clamp(save_geometry, get_output()->workarea->get_workarea()); wf::get_core().default_wm->update_last_windowed_geometry({this}, save_geometry); } @@ -291,9 +288,9 @@ class wayfire_xwayland_view : public wf::toplevel_view_interface_t, public wayfi void handle_map_request(wlr_surface*) override { LOGC(VIEWS, "Start mapping ", self()); - this->main_surface = std::make_shared(xw->surface, false); + this->main_surface = std::make_shared(xw->surface, false); priv->set_mapped_surface_contents(main_surface); - toplevel->set_main_surface(main_surface); + toplevel->set_main_surface(std::dynamic_pointer_cast(main_surface)); toplevel->pending().mapped = true; bool map_maximized = xw->maximized_horz || xw->maximized_vert; @@ -311,11 +308,7 @@ class wayfire_xwayland_view : public wf::toplevel_view_interface_t, public wayfi xw->surface->current.height }; - if (get_output()) - { - desired_geometry = desired_geometry + -wf::origin(get_output()->get_layout_geometry()); - } - + desired_geometry = wf::xw::calculate_wayfire_geometry(get_output(), desired_geometry); wf::adjust_view_pending_geometry_on_start_map(this, desired_geometry, map_fs, map_maximized); wf::get_core().tx_manager->schedule_object(toplevel); } @@ -452,10 +445,10 @@ class wayfire_xwayland_view : public wf::toplevel_view_interface_t, public wayfi if (wo) { wo->connect(&output_geometry_changed); - toplevel->set_output_offset(wf::origin(wo->get_layout_geometry())); + toplevel->update_output(wo); } else { - toplevel->set_output_offset({0, 0}); + toplevel->update_output(nullptr); } } diff --git a/src/view/xwayland/xwayland-toplevel.cpp b/src/view/xwayland/xwayland-toplevel.cpp index a49773c12..7c68a1f32 100644 --- a/src/view/xwayland/xwayland-toplevel.cpp +++ b/src/view/xwayland/xwayland-toplevel.cpp @@ -5,6 +5,7 @@ #include #include "../view-impl.hpp" #include "wayfire/toplevel.hpp" +#include "core/xdg-output-management.hpp" #if WF_HAS_XWAYLAND @@ -27,7 +28,7 @@ wf::xw::xwayland_toplevel_t::xwayland_toplevel_t(wlr_xwayland_surface *xw) } void wf::xw::xwayland_toplevel_t::set_main_surface( - std::shared_ptr main_surface) + std::shared_ptr main_surface) { this->main_surface = main_surface; on_surface_commit.disconnect(); @@ -50,12 +51,44 @@ void wf::xw::xwayland_toplevel_t::set_main_surface( _pending.geometry.height = size.height; _current.geometry.height = size.height; _committed.geometry.height = size.height; + main_surface->set_scale(output_scale); } } -void wf::xw::xwayland_toplevel_t::set_output_offset(wf::point_t output_offset) +void wf::xw::xwayland_toplevel_t::update_output(wf::output_t *output) { - this->output_offset = output_offset; + if (output) + { + static wf::option_wrapper_t force_xwayland_scaling{"workarounds/force_xwayland_scaling"}; + if (force_xwayland_scaling) + { + auto data = output->get_data_safe(); + if (data->geometry.has_value()) + { + this->output_offset = wf::origin(data->geometry.value()); + } else + { + LOGW("Xwayland geometry not set for output ", output->to_string(), ", using (0,0) as offset"); + this->output_offset = {0, 0}; + } + + this->output_scale = output->get_scale(); + } else + { + this->output_offset = wf::origin(output->get_layout_geometry()); + this->output_scale = 1.0; + } + } else + { + this->output_offset = {0, 0}; + this->output_scale = 1.0; + } + + if (main_surface) + { + main_surface->set_scale(output_scale); + } + if (pending().mapped) { // We want to reconfigure xwayland surfaces with output changes only if they are mapped. @@ -74,8 +107,8 @@ void wf::xw::xwayland_toplevel_t::request_native_size() if ((xw->size_hints->base_width > 0) && (xw->size_hints->base_height > 0)) { - this->pending().geometry.width = xw->size_hints->base_width; - this->pending().geometry.height = xw->size_hints->base_height; + this->pending().geometry.width = std::min(1, int(xw->size_hints->base_width / output_scale)); + this->pending().geometry.height = std::min(1, int(xw->size_hints->base_height / output_scale)); wf::get_core().tx_manager->schedule_object(this->shared_from_this()); } } @@ -145,7 +178,7 @@ void wf::xw::xwayland_toplevel_t::reconfigure_xwayland_surface() } const wf::geometry_t configure = - shrink_geometry_by_margins(_pending.geometry, _pending.margins) + output_offset; + shrink_geometry_by_margins(_pending.geometry, _pending.margins) * output_scale + output_offset; if ((configure.width <= 0) || (configure.height <= 0)) { @@ -288,7 +321,10 @@ wf::dimensions_t wf::xw::xwayland_toplevel_t::get_current_xw_size() } auto surf = main_surface->get_surface(); - wf::dimensions_t size = wf::dimensions_t{surf->current.width, surf->current.height}; + wf::dimensions_t size = wf::dimensions_t{ + int(surf->current.width / output_scale), + int(surf->current.height / output_scale) + }; return size; } @@ -297,8 +333,8 @@ wf::dimensions_t wf::xw::xwayland_toplevel_t::get_min_size() if (xw && xw->size_hints) { return wf::dimensions_t{ - std::max(0, xw->size_hints->min_width), - std::max(0, xw->size_hints->min_height) + std::max(0, int(xw->size_hints->min_width / output_scale)), + std::max(0, int(xw->size_hints->min_height / output_scale)) }; } @@ -310,8 +346,8 @@ wf::dimensions_t wf::xw::xwayland_toplevel_t::get_max_size() if (xw && xw->size_hints) { return wf::dimensions_t{ - std::max(0, xw->size_hints->max_width), - std::max(0, xw->size_hints->max_height) + std::max(0, int(xw->size_hints->max_width / output_scale)), + std::max(0, int(xw->size_hints->max_height / output_scale)) }; } diff --git a/src/view/xwayland/xwayland-toplevel.hpp b/src/view/xwayland/xwayland-toplevel.hpp index 505dae87d..b079bf39a 100644 --- a/src/view/xwayland/xwayland-toplevel.hpp +++ b/src/view/xwayland/xwayland-toplevel.hpp @@ -1,6 +1,7 @@ #pragma once #include "config.h" +#include "view/xwayland/xwayland-surface.hpp" #include "wayfire/geometry.hpp" #include "wayfire/util.hpp" #include @@ -32,15 +33,15 @@ class xwayland_toplevel_t : public wf::toplevel_t, public std::enable_shared_fro wf::dimensions_t get_min_size() override; wf::dimensions_t get_max_size() override; - void set_main_surface(std::shared_ptr main_surface); - void set_output_offset(wf::point_t output_offset); + void set_main_surface(std::shared_ptr main_surface); + void update_output(wf::output_t *output); wf::geometry_t calculate_base_geometry(); void request_native_size(); private: - std::shared_ptr main_surface; + std::shared_ptr main_surface; scene::surface_state_t pending_state; void apply_pending_state(); @@ -51,6 +52,7 @@ class xwayland_toplevel_t : public wf::toplevel_t, public std::enable_shared_fro wf::wl_idle_call idle_ready; wlr_xwayland_surface *xw; + float output_scale = 1.0; wf::point_t output_offset = {0, 0}; void handle_surface_commit(); diff --git a/src/view/xwayland/xwayland-unmanaged-view.hpp b/src/view/xwayland/xwayland-unmanaged-view.hpp index ff8f56fe1..68e8b2fcc 100644 --- a/src/view/xwayland/xwayland-unmanaged-view.hpp +++ b/src/view/xwayland/xwayland-unmanaged-view.hpp @@ -1,6 +1,7 @@ #pragma once #include "config.h" +#include "view/xwayland/xwayland-surface.hpp" // IWYU pragma: keep #include "wayfire/core.hpp" #include "wayfire/output.hpp" #include "wayfire/unstable/translation-node.hpp" @@ -99,22 +100,19 @@ class wayfire_unmanaged_xwayland_view : public wayfire_xwayland_view_internal_ba void update_geometry_from_xsurface() { + static wf::option_wrapper_t force_xwayland_scaling{"workarounds/force_xwayland_scaling"}; wf::region_t damage_region = last_bounding_box; // last bounding box damage_region |= get_bounding_box(); // in case resize happened since last move wf::scene::damage_node(get_root_node(), damage_region); - wf::point_t new_position = {xw->x, xw->y}; - - // Move to the correct output, if the xsurface has changed geometry - wf::pointf_t midpoint = {xw->x + xw->width / 2.0, xw->y + xw->height / 2.0}; - wf::output_t *wo = wf::get_core().output_layout->get_output_coords_at(midpoint, midpoint); - - if (wo) + auto wo = wf::xw::find_xwayland_surface_output(xw); + auto new_geometry = wf::xw::calculate_wayfire_geometry(wo, {xw->x, xw->y, xw->width, xw->height}); + surface_root_node->set_offset(wf::origin(new_geometry)); + if (main_surface && force_xwayland_scaling) { - new_position = new_position - wf::origin(wo->get_layout_geometry()); + main_surface->set_scale(wo ? wo->get_scale() : 1.0); } - surface_root_node->set_offset(new_position); if (wo != get_output()) { LOGC(XWL, "Transferring xwayland unmanaged surface ", self(), @@ -126,7 +124,7 @@ class wayfire_unmanaged_xwayland_view : public wayfire_xwayland_view_internal_ba } } - LOGC(XWL, "Xwayland unmanaged surface ", self(), " new position ", new_position); + LOGC(XWL, "Xwayland unmanaged surface ", self(), " new geometry ", new_geometry); last_bounding_box = get_bounding_box(); wf::scene::damage_node(get_root_node(), last_bounding_box); wf::scene::update(surface_root_node, wf::scene::update_flag::GEOMETRY); @@ -158,10 +156,17 @@ class wayfire_unmanaged_xwayland_view : public wayfire_xwayland_view_internal_ba void handle_map_request(wlr_surface *surface) override { + static wf::option_wrapper_t force_xwayland_scaling{"workarounds/force_xwayland_scaling"}; + LOGC(XWL, "Mapping unmanaged xwayland surface ", self()); update_geometry_from_xsurface(); do_map(surface, true, false); + if (main_surface && get_output() && force_xwayland_scaling) + { + main_surface->set_scale(get_output()->get_scale()); + } + /* We update the keyboard focus before emitting the map event, so that * plugins can detect that this view can have keyboard focus. * diff --git a/src/view/xwayland/xwayland-view-base.cpp b/src/view/xwayland/xwayland-view-base.cpp index c958619b1..f5d7849df 100644 --- a/src/view/xwayland/xwayland-view-base.cpp +++ b/src/view/xwayland/xwayland-view-base.cpp @@ -1,3 +1,4 @@ +#include "view/xwayland/xwayland-surface.hpp" #include "wayfire/unstable/xwl-toplevel-base.hpp" #include "wayfire/view-helpers.hpp" #include "../view-impl.hpp" @@ -45,7 +46,7 @@ void wf::xwayland_view_base_t::do_map(wlr_surface *surface, bool autocommit, boo { if (!this->main_surface) { - this->main_surface = std::make_shared(xw->surface, autocommit); + this->main_surface = std::make_shared(xw->surface, autocommit); priv->set_mapped_surface_contents(main_surface); }