Fix window wrapping by implementing a minimal ICCCM window manager.

This commit is contained in:
Rémi Bernon
2025-02-27 12:29:02 +01:00
committed by Robbert van der Helm
parent 918d24a16e
commit 2c6c21409c
2 changed files with 101 additions and 1 deletions
+94 -1
View File
@@ -77,7 +77,8 @@ constexpr uint32_t parent_event_mask =
* slightly when the mouse is already inside of the editor window when * slightly when the mouse is already inside of the editor window when
* opening it. * opening it.
*/ */
constexpr uint32_t wrapper_event_mask = XCB_EVENT_MASK_STRUCTURE_NOTIFY | constexpr uint32_t wrapper_event_mask = XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT |
XCB_EVENT_MASK_STRUCTURE_NOTIFY |
XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_KEY_PRESS |
XCB_EVENT_MASK_KEY_RELEASE; XCB_EVENT_MASK_KEY_RELEASE;
@@ -268,6 +269,8 @@ Editor::Editor(MainContext& main_context,
dnd_proxy_handle_(WineXdndProxy::get_handle()), dnd_proxy_handle_(WineXdndProxy::get_handle()),
client_area_(get_maximum_screen_dimensions(*x11_connection_)), client_area_(get_maximum_screen_dimensions(*x11_connection_)),
wrapper_window_size_({128, 128}), wrapper_window_size_({128, 128}),
host_window_config_({}),
parent_window_config_({}),
// Create a window without any decoratiosn for easy embedding. The // Create a window without any decoratiosn for easy embedding. The
// combination of `WS_EX_TOOLWINDOW` and `WS_POPUP` causes the window to // combination of `WS_EX_TOOLWINDOW` and `WS_POPUP` causes the window to
// be drawn without any decorations (making resizes behave as you'd // be drawn without any decorations (making resizes behave as you'd
@@ -427,6 +430,8 @@ void Editor::resize(uint16_t width, uint16_t height) {
const std::array<uint32_t, 2> values{width, height}; const std::array<uint32_t, 2> values{width, height};
xcb_configure_window(x11_connection_.get(), wrapper_window_.window_, xcb_configure_window(x11_connection_.get(), wrapper_window_.window_,
value_mask, values.data()); value_mask, values.data());
xcb_configure_window(x11_connection_.get(), wine_window_,
value_mask, values.data());
xcb_flush(x11_connection_.get()); xcb_flush(x11_connection_.get());
// NOTE: This lets us skip resize requests in CLAP plugins when the plugin // NOTE: This lets us skip resize requests in CLAP plugins when the plugin
@@ -558,6 +563,47 @@ void Editor::handle_x11_events() noexcept {
std::to_string(event->window); std::to_string(event->window);
}); });
// If the host window is different from the parent window then
// the Wine window is at a non-zero offset from the top-left
// corner. The host window will always receive absolute position
// information in its events, sent from the window manager, while
// the parent window might receive position changes relative to
// the host window when it is a child window.
if (event->window == host_window_ &&
is_synthetic_event) {
host_window_config_ = *event;
}
if (event->window == parent_window_ &&
host_window_ != parent_window_ &&
!is_synthetic_event) {
parent_window_config_ = *event;
}
// Window managers are expected to send ConfigureNotify to
// their managed windows whenever the window is being moved
// or resized by the user, so that application don't have to
// do all the relative positioning computation themselves.
// Wine also expects this and ignores position changes on its
// window parents, and its window position would get out of
// sync without this event.
if (event->window == host_window_ ||
event->window == parent_window_) {
xcb_configure_notify_event_t translated_event{};
translated_event.response_type = XCB_CONFIGURE_NOTIFY;
translated_event.event = wine_window_;
translated_event.window = wine_window_;
translated_event.width = event->width;
translated_event.height = event->height;
translated_event.x = host_window_config_.x + parent_window_config_.x;
translated_event.y = host_window_config_.y + parent_window_config_.y;
xcb_send_event(
x11_connection_.get(), false, wine_window_,
XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY,
reinterpret_cast<char*>(&translated_event));
xcb_flush(x11_connection_.get());
}
if (event->window == host_window_ || if (event->window == host_window_ ||
event->window == parent_window_ || event->window == parent_window_ ||
event->window == wrapper_window_.window_) { event->window == wrapper_window_.window_) {
@@ -580,6 +626,53 @@ void Editor::handle_x11_events() noexcept {
} }
} }
} break; } break;
// We're listening for `ConfigureRequest` events on the
// wrapper window. This is received whenever Wine wants
// to configure its window, and we need to adjust the
// configuration so that it stays within our wrapper.
// Here, wwe could translate window position changes by
// moving the wrapper window itself but this isn't really
// necessary. Instead, we prevent Wine from actually moving
// its window.
case XCB_CONFIGURE_REQUEST: {
const auto event =
reinterpret_cast<xcb_configure_request_event_t*>(
generic_event.get());
logger_.log_editor_trace([&]() {
return "DEBUG: ConfigureRequest for window " +
std::to_string(event->window);
});
const uint16_t value_mask = XCB_CONFIG_WINDOW_X |
XCB_CONFIG_WINDOW_Y |
XCB_CONFIG_WINDOW_WIDTH |
XCB_CONFIG_WINDOW_HEIGHT;
const std::array<uint32_t, 4> values{0, 0, event->width, event->height};
xcb_configure_window(x11_connection_.get(), wine_window_,
value_mask, values.data());
xcb_flush(x11_connection_.get());
} break;
// We're listening for `MapRequest` events on the wrapper
// window. This is received whenever Wine wants to map its
// window, and we need to forward the request to the X server.
// Wine also expects the window manager to change the WM_STATE
// property whenever it has finished mapping the window. We
// effectively implement a sub window manager here, so update
// the property as we should.
case XCB_MAP_REQUEST: {
const auto event =
reinterpret_cast<xcb_map_request_event_t*>(
generic_event.get());
logger_.log_editor_trace([&]() {
return "DEBUG: MapRequest for window " +
std::to_string(event->window);
});
xcb_map_window(x11_connection_.get(), wine_window_);
const std::array<uint32_t, 2> values{XCB_ICCCM_WM_STATE_NORMAL, 0};
xcb_change_property(x11_connection_.get(), XCB_PROP_MODE_REPLACE, wine_window_, xcb_wm_state_property_,
xcb_wm_state_property_, 32, 2, values.data());
xcb_flush(x11_connection_.get());
} break;
// Start the XEmbed procedure when the window becomes visible, // Start the XEmbed procedure when the window becomes visible,
// since most hosts will only show the window after the plugin // since most hosts will only show the window after the plugin
// has embedded itself into it. // has embedded itself into it.
+7
View File
@@ -27,6 +27,7 @@
#pragma push_macro("_WIN32") #pragma push_macro("_WIN32")
#undef _WIN32 #undef _WIN32
#include <xcb/xcb.h> #include <xcb/xcb.h>
#include <xcb/xcb_icccm.h>
#pragma pop_macro("_WIN32") #pragma pop_macro("_WIN32")
#include "../common/configuration.h" #include "../common/configuration.h"
@@ -394,6 +395,12 @@ class Editor {
*/ */
Size wrapper_window_size_; Size wrapper_window_size_;
/**
* Last received configurations for the host and parent windows.
*/
xcb_configure_notify_event_t host_window_config_;
xcb_configure_notify_event_t parent_window_config_;
/** /**
* The handle for the window created through Wine that the plugin uses to * The handle for the window created through Wine that the plugin uses to
* embed itself in. * embed itself in.