From 2c1dca387fa80418471ec7ff50eba52e69fc3ed7 Mon Sep 17 00:00:00 2001 From: lkn Date: Sat, 6 Jun 2026 20:05:57 +0200 Subject: [PATCH] multi monitor --- src/Makefile.am | 1 + src/backend/wayland/wl_backend.c | 82 ++++++++++++++++++---------- src/backend/wayland/wl_background.c | 57 ++++++++++++++----- src/backend/wayland/wl_dock.c | 4 +- src/backend/wayland/wl_extensions.c | 4 ++ src/backend/wayland/wl_framebuf.c | 40 +++++++++++++- src/backend/wayland/wl_handlers.c | 79 ++++++++++++++------------- src/backend/wayland/wl_layout_sync.c | 30 ++++++++++ src/backend/wayland/wl_output.c | 65 ++++++++++++++++++++-- src/backend/wayland/wl_output_mgmt.c | 80 ++++++++++++++++++++++----- src/backend/wayland/wl_overlay.c | 30 +++++++--- src/backend/wayland/wl_types.h | 4 ++ src/backend/wayland/wl_xwayland.c | 9 ++- src/dock.c | 75 +++++++++++++------------ 14 files changed, 412 insertions(+), 148 deletions(-) create mode 100644 src/backend/wayland/wl_layout_sync.c diff --git a/src/Makefile.am b/src/Makefile.am index 1044424..bf933e4 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -201,6 +201,7 @@ wmaker_SOURCES += \ backend/wayland/wl_xdg_activation.c \ backend/wayland/wl_output_mgmt.c \ backend/wayland/wl_output.c \ + backend/wayland/wl_layout_sync.c \ backend/wayland/wl_xdg.c \ backend/wayland/wl_dock.c \ backend/wayland/wl_layer_shell.c \ diff --git a/src/backend/wayland/wl_backend.c b/src/backend/wayland/wl_backend.c index 7c3395f..1eedcc8 100644 --- a/src/backend/wayland/wl_backend.c +++ b/src/backend/wayland/wl_backend.c @@ -411,6 +411,12 @@ wl_display_open(const char *name) * Must be created before backend start so handle_backend_new_output * can add the output to the layout when it fires. */ wl_state.output_layout = wlr_output_layout_create(wl_state.display); + if (wl_state.output_layout) { + extern void wl_handle_output_layout_change(struct wl_listener *listener, void *data); + wl_state.output_layout_change_listener.notify = wl_handle_output_layout_change; + wl_signal_add(&wl_state.output_layout->events.change, + &wl_state.output_layout_change_listener); + } /* create hardware cursor tracker and load xcursor theme. * wlr_cursor tracks the logical cursor position across the output @@ -441,6 +447,9 @@ wl_display_open(const char *name) * when the output fires (for nested Wayland, new_output fires during * wlr_backend_start via the host compositor roundtrip). */ wl_state.scene = wlr_scene_create(); + if (wl_state.scene && wl_state.output_layout) + wl_state.scene_output_layout = + wlr_scene_attach_output_layout(wl_state.scene, wl_state.output_layout); /* Register commit callback for WINGs views (defined later in file) */ { extern void (*W_ViewCommitBackingFn)(unsigned long id); @@ -495,11 +504,49 @@ wl_display_open(const char *name) wl_signal_add(&wl_state.backend->events.new_output, &wl_state.backend_new_output_listener); - /* For nested mode, queue an output window before start; wlr_wl_output_create - * returns NULL before the backend is initialised — the output is created - * during wlr_backend_start and delivered via new_output. */ - if (wl_state.is_nested && wl_state.wl_backend_inner) - wlr_wl_output_create(wl_state.wl_backend_inner); + wl_list_init(&wl_state.toplevel_list); + wl_list_init(&wl_state.or_list); + wl_list_init(&wl_state.frame_list); + wl_list_init(&wl_state.layer_list); + wl_list_init(&wl_state.outputs); + wl_list_init(&wl_state.real_pointer_listeners); + wl_state.output_count = 0; + { + int ki; + for (ki = 0; ki < 3; ki++) + wl_list_init(&wl_state.ctx_lists[ki]); + } + wl_state.next_synthetic_id = 0x10000000UL; + wl_state.managed_surface_count = 0; + + /* For nested mode, queue output windows before start; nested wlroots + * backends don't automatically honor WLR_WL_OUTPUTS for us here, so + * create one wl_output per requested slot. If the environment variable + * is a plain integer use that count; otherwise treat any non-empty value + * as a comma-separated list of output specs and create one output per + * comma-delimited entry. */ + if (wl_state.is_nested && wl_state.wl_backend_inner) { + const char *outs_env = getenv("WLR_WL_OUTPUTS"); + int requested = 1; + + if (outs_env && *outs_env) { + char *end = NULL; + long n = strtol(outs_env, &end, 10); + if (end && *end == '\0' && n > 0) { + requested = (int)n; + } else { + int count = 1; + for (const char *p = outs_env; *p; ++p) { + if (*p == ',') + count++; + } + requested = count; + } + } + + for (int i = 0; i < requested; i++) + wlr_wl_output_create(wl_state.wl_backend_inner); + } if (!wlr_backend_start(wl_state.backend)) { wwarning("wl_backend: failed to start backend"); @@ -572,20 +619,6 @@ wl_display_open(const char *name) wlr_seat_set_capabilities(wl_state.seat, WL_SEAT_CAPABILITY_KEYBOARD | WL_SEAT_CAPABILITY_POINTER); - wl_list_init(&wl_state.toplevel_list); - wl_list_init(&wl_state.or_list); - wl_list_init(&wl_state.frame_list); - wl_list_init(&wl_state.layer_list); - wl_list_init(&wl_state.outputs); - wl_state.output_count = 0; - { - int ki; - for (ki = 0; ki < 3; ki++) - wl_list_init(&wl_state.ctx_lists[ki]); - } - wl_state.next_synthetic_id = 0x10000000UL; - wl_state.managed_surface_count = 0; - wl_state.xdg_new_surface_listener.notify = handle_xdg_new_toplevel; wl_signal_add(&wl_state.xdg_shell->events.new_toplevel, &wl_state.xdg_new_surface_listener); @@ -857,15 +890,11 @@ wl_display_close(void) WL_LISTENER_REMOVE(wl_state.backend_new_input_listener); WL_LISTENER_REMOVE(wl_state.real_keyboard_key_listener); WL_LISTENER_REMOVE(wl_state.real_keyboard_modifiers_listener); - WL_LISTENER_REMOVE(wl_state.real_pointer_motion_abs_listener); - WL_LISTENER_REMOVE(wl_state.real_pointer_button_listener); - WL_LISTENER_REMOVE(wl_state.real_pointer_frame_listener); WL_LISTENER_REMOVE(wl_state.seat_request_cursor_listener); WL_LISTENER_REMOVE(wl_state.seat_request_set_selection_listener); WL_LISTENER_REMOVE(wl_state.seat_request_primary_selection_listener); WL_LISTENER_REMOVE(wl_state.idle_new_inhibitor_listener); WL_LISTENER_REMOVE(wl_state.pointer_axis_listener); - WL_LISTENER_REMOVE(wl_state.real_pointer_axis_listener); WL_LISTENER_REMOVE(wl_state.new_constraint_listener); WL_LISTENER_REMOVE(wl_state.new_shortcuts_inhibitor_listener); WL_LISTENER_REMOVE(wl_state.output_mgr_apply_listener); @@ -1133,13 +1162,6 @@ wl_event_loop_run(void) wwarning("wl_backend: event loop exited"); - /* Remove pointer listeners before wlroots destroys the pointer device */ - if (wl_state.pointer_dev) { - wl_list_remove(&wl_state.real_pointer_motion_abs_listener.link); - wl_list_remove(&wl_state.real_pointer_button_listener.link); - wl_list_remove(&wl_state.real_pointer_frame_listener.link); - wl_list_remove(&wl_state.real_pointer_axis_listener.link); - } } void diff --git a/src/backend/wayland/wl_background.c b/src/backend/wayland/wl_background.c index 26bc1dd..70dd22b 100644 --- a/src/backend/wayland/wl_background.c +++ b/src/backend/wayland/wl_background.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -82,8 +83,8 @@ static int parse_color_to_rcolor(WScreen *scr, const char *name, RColor *out) } - int width = wl_state.output->width; - int height = wl_state.output->height; + int width = scr->scr_width > 0 ? scr->scr_width : wl_state.output->width; + int height = scr->scr_height > 0 ? scr->scr_height : wl_state.output->height; /* Parse the texture proplist */ WMPropList *texpl = WMCreatePropListFromDescription(texture_desc); @@ -104,16 +105,34 @@ static int parse_color_to_rcolor(WScreen *scr, const char *name, RColor *out) RColor color = {0x50, 0x50, 0x75, 0xFF}; if (colorstr) parse_color_to_rcolor(scr, colorstr, &color); - /* For solid colors, just update the scene rect directly */ - if (wl_state.bg_rect) { + /* For solid colors, update every output-local background rect. */ + { float c[4] = { color.red/255.0f, color.green/255.0f, color.blue/255.0f, 1.0f }; - wlr_scene_rect_set_color(wl_state.bg_rect, c); - wlr_scene_node_set_enabled(&wl_state.bg_rect->node, true); + struct wl_wlr_output_state *ost; + wl_list_for_each(ost, &wl_state.outputs, link) { + if (!ost->bg_rect || !wl_state.output_layout) + continue; + struct wlr_box box = {0}; + wlr_output_layout_get_box(wl_state.output_layout, ost->output, &box); + if (box.width <= 0 || box.height <= 0) { + box.width = (int)ost->output->width; + box.height = (int)ost->output->height; + } + wlr_scene_rect_set_size(ost->bg_rect, box.width, box.height); + wlr_scene_node_set_position(&ost->bg_rect->node, box.x, box.y); + wlr_scene_rect_set_color(ost->bg_rect, c); + wlr_scene_node_set_enabled(&ost->bg_rect->node, true); + } if (wl_state.bg_image) wlr_scene_node_set_enabled(&wl_state.bg_image->node, false); } - if (wl_state.output) - wlr_output_schedule_frame(wl_state.output); + { + struct wl_wlr_output_state *ost; + wl_list_for_each(ost, &wl_state.outputs, link) { + if (ost->output) + wlr_output_schedule_frame(ost->output); + } + } WMReleasePropList(texpl); return; } else if ((strcasecmp(type, "vgradient") == 0 || @@ -302,9 +321,14 @@ static int parse_color_to_rcolor(WScreen *scr, const char *name, RColor *out) wlr_buffer_init(&buf->base, &bg_buffer_impl, width, height); - /* Hide the solid-color rect */ - if (wl_state.bg_rect) - wlr_scene_node_set_enabled(&wl_state.bg_rect->node, false); + /* Hide per-output solid-color rects while the image background is active. */ + { + struct wl_wlr_output_state *ost; + wl_list_for_each(ost, &wl_state.outputs, link) { + if (ost->bg_rect) + wlr_scene_node_set_enabled(&ost->bg_rect->node, false); + } + } /* Replace or create the background scene buffer */ if (wl_state.bg_image) { @@ -320,7 +344,12 @@ static int parse_color_to_rcolor(WScreen *scr, const char *name, RColor *out) wlr_buffer_drop(wl_state.bg_wlr_buf); wl_state.bg_wlr_buf = &buf->base; - /* Schedule a frame to display the new background immediately */ - if (wl_state.output) - wlr_output_schedule_frame(wl_state.output); + /* Schedule all outputs so the new background is shown everywhere. */ + { + struct wl_wlr_output_state *ost; + wl_list_for_each(ost, &wl_state.outputs, link) { + if (ost->output) + wlr_output_schedule_frame(ost->output); + } + } } diff --git a/src/backend/wayland/wl_dock.c b/src/backend/wayland/wl_dock.c index bc92d76..688257f 100644 --- a/src/backend/wayland/wl_dock.c +++ b/src/backend/wayland/wl_dock.c @@ -105,8 +105,8 @@ wm_dock_create(WScreen *scr) if (!wl_state.scene || !wl_state.output) return; - int sw = (int)wl_state.output->width; - int sh = (int)wl_state.output->height; + int sw = scr->scr_width > 0 ? scr->scr_width : (int)wl_state.output->width; + int sh = scr->scr_height > 0 ? scr->scr_height : (int)wl_state.output->height; int icon_sz = (wPreferences.icon_size > 0) ? wPreferences.icon_size : 64; int dw = icon_sz + 4; /* dock width = icon + 2px pad each side */ diff --git a/src/backend/wayland/wl_extensions.c b/src/backend/wayland/wl_extensions.c index a16ee66..cf897c0 100644 --- a/src/backend/wayland/wl_extensions.c +++ b/src/backend/wayland/wl_extensions.c @@ -362,6 +362,10 @@ wl_protocols_init(void) wl_state.output_mgr_test_listener.notify = handle_output_mgr_test; wl_signal_add(&wl_state.output_mgr->events.test, &wl_state.output_mgr_test_listener); + { + extern void wl_output_manager_broadcast_initial_config(void); + wl_output_manager_broadcast_initial_config(); + } wwarning("wl_backend: output-management created (zwlr_output_management_v1)"); } wl_state.output_power_mgr = wlr_output_power_manager_v1_create(wl_state.display); diff --git a/src/backend/wayland/wl_framebuf.c b/src/backend/wayland/wl_framebuf.c index 78bd866..e97bf46 100644 --- a/src/backend/wayland/wl_framebuf.c +++ b/src/backend/wayland/wl_framebuf.c @@ -222,6 +222,21 @@ frame_buf_at(int px, int py) * Returns the button fb whose absolute rect encloses (px,py) with the * smallest area (most specific), or NULL if no button is under the pointer. */ +void +wl_framebuf_refresh_all_positions(void) +{ + struct wl_frame_buf *fb; + wl_list_for_each(fb, &wl_state.frame_list, link) { + if (fb->parent_id) + continue; + if (fb->tree) + wlr_scene_node_set_position(&fb->tree->node, fb->x, fb->y); + else if (fb->scene_buf) + wlr_scene_node_set_position(&fb->scene_buf->node, fb->x, fb->y); + frame_buf_update_children_abs(fb->id); + } +} + struct wl_frame_buf * frame_buf_button_at(int px, int py) { @@ -261,11 +276,32 @@ frame_buf_button_at(int px, int py) * back to the scene-graph-based frame_buf_at for titlebars, resizebars, and * top-level frames. */ +static void +wl_dump_hit_debug(int px, int py, struct wl_frame_buf *fb) +{ + if (!fb) + return; + wwarning("hit: ptr=(%d,%d) fb=0x%lx abs=(%d,%d) rel=(%d,%d) size=(%d,%d) parent=0x%lx", + px, py, (unsigned long)fb->id, fb->x, fb->y, fb->rel_x, fb->rel_y, + fb->width, fb->height, (unsigned long)fb->parent_id); + if (fb->parent_id) { + struct wl_frame_buf *parent = frame_buf_find(fb->parent_id); + if (parent) { + wwarning("hit: parent fb=0x%lx abs=(%d,%d) size=(%d,%d)", + (unsigned long)parent->id, parent->x, parent->y, + parent->width, parent->height); + } + } +} + struct wl_frame_buf * frame_buf_hit(int px, int py) { - struct wl_frame_buf *fb = frame_buf_button_at(px, py); - return fb ? fb : frame_buf_at(px, py); + struct wl_frame_buf *fb = frame_buf_at(px, py); + if (!fb) + fb = frame_buf_button_at(px, py); + wl_dump_hit_debug(px, py, fb); + return fb; } /* ------------------------------------------------------------------ */ diff --git a/src/backend/wayland/wl_handlers.c b/src/backend/wayland/wl_handlers.c index 71cd29a..73156af 100644 --- a/src/backend/wayland/wl_handlers.c +++ b/src/backend/wayland/wl_handlers.c @@ -135,6 +135,11 @@ handle_pointer_button(struct wl_listener *listener, void *data) { (void)listener; struct wlr_pointer_button_event *ev = data; + wwarning("ptr-button: dev=%p output=%s button=%u state=%u ptr=(%d,%d) grab=%d", + (void*)&ev->pointer->base, + ev->pointer->output_name ? ev->pointer->output_name : "(none)", + ev->button, (unsigned)ev->state, + wl_state.pointer_x, wl_state.pointer_y, wl_state.grab_type); if (!wl_state.seat) return; /* any button release ends an active compositor grab (move/resize). @@ -205,30 +210,15 @@ handle_pointer_button(struct wl_listener *listener, void *data) wev.type = (ev->state == WLR_BUTTON_PRESSED) ? WME_BUTTON_PRESS : WME_BUTTON_RELEASE; - /* Hit-test: determine which WM window was clicked. - * Use frame_buf_hit so button sub-windows (invisible to the scene - * graph because their pixels are composited into the titlebar buffer) - * are resolved to their own window IDs, letting handleButtonPress - * route the event to buttonMouseDown instead of titlebarMouseDown. */ - struct wl_frame_buf *fb = frame_buf_hit(wl_state.pointer_x, - wl_state.pointer_y); - if (fb) { - wev.window = fb->id; - wev.u.button.window = fb->id; - wev.u.button.x = wl_state.pointer_x - fb->x; - wev.u.button.y = wl_state.pointer_y - fb->y; - } else { - /* No frame buffer — check if the click landed on an XDG toplevel - * and look up its WINGs view ID from the frame list. */ - double _sx, _sy; + /* Hit-test: prefer scene-local coordinates when a client/view surface + * is under the pointer. Fall back to frame_buf-only hit testing for + * titlebars, buttons, resizebars, menus, and other WM-internal nodes. */ + { + double _sx = 0.0, _sy = 0.0; struct wl_toplevel_view *_tv = NULL; - view_surface_at(wl_state.pointer_x, wl_state.pointer_y, - &_sx, &_sy, &_tv); - if (_tv) { - /* Try to find a WINGs view frame buffer at this position. - * The compositor's view ID (_tv->id) is synthetic and not - * known to WINGs, so we match by bounds against frame_buf - * entries with WINGs view IDs (>= 0x20000000). */ + struct wlr_surface *_surf = view_surface_at(wl_state.pointer_x, wl_state.pointer_y, + &_sx, &_sy, &_tv); + if (_surf && _tv) { struct wl_frame_buf *_wtb = NULL; wl_list_for_each(_wtb, &wl_state.frame_list, link) { if (_wtb->id >= 0x20000000 && @@ -236,26 +226,39 @@ handle_pointer_button(struct wl_listener *listener, void *data) wl_state.pointer_x < _wtb->x + _wtb->width && wl_state.pointer_y >= _wtb->y && wl_state.pointer_y < _wtb->y + _wtb->height) { - wwarning("btn: wings fb id=0x%lx xy=(%d,%d) wh=(%d,%d) click=(%.0f,%.0f)", - (unsigned long)_wtb->id, _wtb->x, _wtb->y, - _wtb->width, _wtb->height, - wl_state.pointer_x, wl_state.pointer_y); break; } } - if (!_wtb) - wwarning("btn: no wings fb found, using tv id=0x%lx", (unsigned long)_tv->id); wev.window = _wtb ? _wtb->id : _tv->id; wev.u.button.window = wev.window; - wev.u.button.x = _wtb ? (wl_state.pointer_x - _wtb->x) : (int)_sx; - wev.u.button.y = _wtb ? (wl_state.pointer_y - _wtb->y) : (int)_sy; + wev.u.button.x = (int)_sx; + wev.u.button.y = (int)_sy; + wwarning("btn-route: ptr=(%d,%d) tv=0x%lx surf=%p sxsy=(%.1f,%.1f) wings_fb=%s final=0x%lx local=(%d,%d)", + wl_state.pointer_x, wl_state.pointer_y, + (unsigned long)_tv->id, (void*)_surf, _sx, _sy, + _wtb ? "yes" : "no", (unsigned long)wev.window, + wev.u.button.x, wev.u.button.y); } else { - WScreen *scr0 = wScreenWithNumber(0); - WNativeWindow root = scr0 ? scr0->root_win : (WNativeWindow)0x0FFFFFFF; - wev.window = root; - wev.u.button.window = root; - wev.u.button.x = wl_state.pointer_x; - wev.u.button.y = wl_state.pointer_y; + struct wl_frame_buf *fb = frame_buf_hit(wl_state.pointer_x, + wl_state.pointer_y); + if (fb) { + wev.window = fb->id; + wev.u.button.window = fb->id; + wev.u.button.x = wl_state.pointer_x - fb->x; + wev.u.button.y = wl_state.pointer_y - fb->y; + wwarning("btn-route: ptr=(%d,%d) fb=0x%lx abs=(%d,%d) final=0x%lx local=(%d,%d)", + wl_state.pointer_x, wl_state.pointer_y, + (unsigned long)fb->id, fb->x, fb->y, + (unsigned long)wev.window, + wev.u.button.x, wev.u.button.y); + } else { + WScreen *scr0 = wScreenWithNumber(0); + WNativeWindow root = scr0 ? scr0->root_win : (WNativeWindow)0x0FFFFFFF; + wev.window = root; + wev.u.button.window = root; + wev.u.button.x = wl_state.pointer_x; + wev.u.button.y = wl_state.pointer_y; + } } } wev.u.button.time = ev->time_msec; @@ -276,6 +279,8 @@ handle_pointer_button(struct wl_listener *listener, void *data) * the fb is a WM fake_leader; look up the WINGs panel via leader. */ struct W_View *target = NULL; WNativeWindow wings_id = 0; + struct wl_frame_buf *fb = frame_buf_hit(wl_state.pointer_x, + wl_state.pointer_y); if (fb && fb->id >= 0x20000000) { wings_id = fb->id; } else if (fb) { diff --git a/src/backend/wayland/wl_layout_sync.c b/src/backend/wayland/wl_layout_sync.c new file mode 100644 index 0000000..d9506ac --- /dev/null +++ b/src/backend/wayland/wl_layout_sync.c @@ -0,0 +1,30 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include +#include "../../screen.h" +#include "wl_types.h" + +extern void wl_screen_refresh_heads(WScreen *scr); +extern void wl_framebuf_refresh_all_positions(void); + +void +wl_handle_output_layout_change(struct wl_listener *listener, void *data) +{ + (void)listener; + (void)data; + + if (wl_state.scr) + wl_screen_refresh_heads(wl_state.scr); + + wwarning("layout-change: pointer=(%d,%d)", wl_state.pointer_x, wl_state.pointer_y); + if (wl_state.scr && wl_state.scr->heads) { + for (int i = 0; i < wl_state.scr->head_count; i++) { + wwarning("layout-change: head[%d]=(%d,%d %dx%d)", i, + wl_state.scr->heads[i].x, wl_state.scr->heads[i].y, + wl_state.scr->heads[i].width, wl_state.scr->heads[i].height); + } + } + + wl_framebuf_refresh_all_positions(); +} diff --git a/src/backend/wayland/wl_output.c b/src/backend/wayland/wl_output.c index 8621448..e021f25 100644 --- a/src/backend/wayland/wl_output.c +++ b/src/backend/wayland/wl_output.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -17,6 +18,33 @@ /* Forward declaration for screen head refresh */ extern void wl_screen_refresh_heads(WScreen *scr); +void +wl_output_manager_broadcast_initial_config(void) +{ + if (!wl_state.output_mgr) + return; + + struct wlr_output_configuration_v1 *config = + wlr_output_configuration_v1_create(); + if (!config) + return; + + struct wl_wlr_output_state *ost; + wl_list_for_each(ost, &wl_state.outputs, link) { + if (!ost->output) + continue; + struct wlr_output_configuration_head_v1 *head = + wlr_output_configuration_head_v1_create(config, ost->output); + if (head && wl_state.output_layout) { + struct wlr_box box = {0}; + wlr_output_layout_get_box(wl_state.output_layout, ost->output, &box); + head->state.x = box.x; + head->state.y = box.y; + } + } + + wlr_output_manager_v1_set_configuration(wl_state.output_mgr, config); +} void handle_output_frame(struct wl_listener *listener, void *data) @@ -47,10 +75,29 @@ handle_output_frame(struct wl_listener *listener, void *data) } } -/* wlr_output_finish asserts that destroy.listener_list is empty, - * so we CANNOT listen to output->events.destroy. Instead the - * frame listener is cleaned up by wlr_scene_output_destroy which - * runs before wlr_output_finish in the teardown chain. */ +static void +handle_output_destroy(struct wl_listener *listener, void *data) +{ + (void)data; + struct wl_wlr_output_state *ost = + wl_container_of(listener, ost, destroy_listener); + + if (ost->frame_listener.link.next) + wl_list_remove(&ost->frame_listener.link); + if (ost->destroy_listener.link.next) + wl_list_remove(&ost->destroy_listener.link); + if (ost->link.next) + wl_list_remove(&ost->link); + if (wl_state.output_count > 0) + wl_state.output_count--; + if (wl_state.output == ost->output) { + wl_state.output = NULL; + wl_state.scene_output = NULL; + wl_state.bg_rect = NULL; + } + wfree(ost); +} + void handle_backend_new_output(struct wl_listener *listener, void *data) { @@ -98,8 +145,17 @@ handle_backend_new_output(struct wl_listener *listener, void *data) if (wl_state.scene) { ost->scene_output = wlr_scene_output_create(wl_state.scene, output); + if (ost->scene_output && wl_state.scene_output_layout && wl_state.output_layout) { + struct wlr_output_layout_output *lo = + wlr_output_layout_get(wl_state.output_layout, output); + if (lo) + wlr_scene_output_layout_add_output(wl_state.scene_output_layout, + lo, ost->scene_output); + } ost->frame_listener.notify = handle_output_frame; wl_signal_add(&output->events.frame, &ost->frame_listener); + ost->destroy_listener.notify = handle_output_destroy; + wl_signal_add(&output->events.destroy, &ost->destroy_listener); wwarning("wl_backend: scene output created for %s (%dx%d)", output->name, output->width, output->height); @@ -123,6 +179,7 @@ handle_backend_new_output(struct wl_listener *listener, void *data) wl_list_insert(&wl_state.outputs, &ost->link); wl_state.output_count++; + wl_output_manager_broadcast_initial_config(); /* For backward compat, keep wl_state.output pointing to the first output */ if (!wl_state.output) { diff --git a/src/backend/wayland/wl_output_mgmt.c b/src/backend/wayland/wl_output_mgmt.c index 38dae40..6f62dd0 100644 --- a/src/backend/wayland/wl_output_mgmt.c +++ b/src/backend/wayland/wl_output_mgmt.c @@ -2,40 +2,94 @@ #include "config.h" #endif #include +#include #include #include +#include +#include +#include #include #include #include "wl_types.h" +extern void wl_screen_refresh_heads(WScreen *scr); +extern void wl_output_manager_broadcast_initial_config(void); + +static bool +wl_output_config_apply(struct wlr_output_configuration_v1 *config, bool test_only) +{ + size_t states_len = 0; + struct wlr_backend_output_state *states = + wlr_output_configuration_v1_build_state(config, &states_len); + if (!states) + return false; + + bool ok; + if (test_only) + ok = wlr_backend_test(wl_state.backend, states, states_len); + else + ok = wlr_backend_commit(wl_state.backend, states, states_len); + + free(states); + if (!ok) + return false; + + struct wlr_output_configuration_head_v1 *head; + wl_list_for_each(head, &config->heads, link) { + struct wlr_output *output = head->state.output; + if (!output || !wl_state.output_layout) + continue; + wlr_output_layout_remove(wl_state.output_layout, output); + if (head->state.enabled) + wlr_output_layout_add(wl_state.output_layout, output, + head->state.x, head->state.y); + } + + if (!test_only) { + struct wl_wlr_output_state *ost; + wl_list_for_each(ost, &wl_state.outputs, link) { + if (!ost->output || !ost->scene_output || !wl_state.scene_output_layout || !wl_state.output_layout) + continue; + struct wlr_output_layout_output *lo = + wlr_output_layout_get(wl_state.output_layout, ost->output); + if (lo) + wlr_scene_output_layout_add_output(wl_state.scene_output_layout, + lo, ost->scene_output); + } + if (wl_state.scr) + wl_screen_refresh_heads(wl_state.scr); + wl_output_manager_broadcast_initial_config(); + } + + return true; +} + /* ------------------------------------------------------------------ */ -/* - * handle_output_mgr_apply — client requests a randr-like output reconfiguration. - * For a single-output headless compositor there is nothing to reconfigure; just - * acknowledge success so the client does not hang waiting for feedback. - * The configuration object is compositor-owned and must be destroyed here. - */ void handle_output_mgr_apply(struct wl_listener *listener, void *data) { (void)listener; struct wlr_output_configuration_v1 *config = data; - wlr_output_configuration_v1_send_succeeded(config); + if (wl_output_config_apply(config, false)) { + wlr_output_configuration_v1_send_succeeded(config); + wwarning("wl_backend: output manager apply request — applied"); + } else { + wlr_output_configuration_v1_send_failed(config); + wwarning("wl_backend: output manager apply request — failed"); + } wlr_output_configuration_v1_destroy(config); - wwarning("wl_backend: output manager apply request — acknowledged (no-op)"); } -/* - * handle_output_mgr_test — client tests a configuration without applying it. - * Always report success (any single-output config is trivially valid). - */ void handle_output_mgr_test(struct wl_listener *listener, void *data) { (void)listener; struct wlr_output_configuration_v1 *config = data; - wlr_output_configuration_v1_send_succeeded(config); + if (wl_output_config_apply(config, true)) + wlr_output_configuration_v1_send_succeeded(config); + else + wlr_output_configuration_v1_send_failed(config); wlr_output_configuration_v1_destroy(config); } diff --git a/src/backend/wayland/wl_overlay.c b/src/backend/wayland/wl_overlay.c index e54b694..b539979 100644 --- a/src/backend/wayland/wl_overlay.c +++ b/src/backend/wayland/wl_overlay.c @@ -143,6 +143,22 @@ const uint8_t ovl_font[96][OVL_GH] = { }; /* Lazily allocate (or resize) the full-screen overlay buffer. */ +static void +wl_virtual_bounds(int *w, int *h) +{ + WScreen *scr = wl_state.scr; + if (scr && scr->scr_width > 0 && scr->scr_height > 0) { + *w = scr->scr_width; + *h = scr->scr_height; + } else if (wl_state.output) { + *w = (int)wl_state.output->width; + *h = (int)wl_state.output->height; + } else { + *w = 1920; + *h = 1080; + } +} + void overlay_ensure(WScreen *scr) { @@ -150,11 +166,8 @@ overlay_ensure(WScreen *scr) if (scr && scr->scr_width > 0 && scr->scr_height > 0) { w = scr->scr_width; h = scr->scr_height; - } else if (wl_state.output) { - w = (int)wl_state.output->width; - h = (int)wl_state.output->height; } else { - w = 1920; h = 1080; + wl_virtual_bounds(&w, &h); } if (wl_state.overlay_surf && wl_state.overlay_surf->width == w && @@ -263,10 +276,11 @@ overlay_flush(void) /* Notify wlroots that the pixel data has changed. */ wlr_scene_buffer_set_buffer_with_damage(s->scene_buf, &s->wlr_buf, NULL); } - /* Commit the scene immediately so the animation step is visible - * before the caller's wusleep() delay. */ - if (wl_state.scene_output && wl_state.allocator) - wlr_scene_output_commit(wl_state.scene_output, NULL); + /* Do not force an immediate commit here. In multi-output mode the + * single cached wl_state.scene_output only refers to the first output, + * and synchronous commits from overlay/grab paths can exhaust the + * nested backend swapchain. Let the normal output frame loop present + * the updated scene. */ } /* ------------------------------------------------------------------ */ diff --git a/src/backend/wayland/wl_types.h b/src/backend/wayland/wl_types.h index d39e502..f8ea1ea 100644 --- a/src/backend/wayland/wl_types.h +++ b/src/backend/wayland/wl_types.h @@ -143,6 +143,7 @@ struct wl_wlr_output_state { struct wlr_scene_output *scene_output; struct wlr_scene_rect *bg_rect; struct wl_listener frame_listener; + struct wl_listener destroy_listener; struct wl_list link; /* wl_compositor_state.outputs */ }; @@ -221,8 +222,10 @@ struct wl_compositor_state { struct wlr_session *session; /* DRM session (standalone only) */ struct wlr_allocator *allocator; struct wlr_scene *scene; + struct wlr_scene_output_layout *scene_output_layout; struct wlr_scene_output *scene_output; /* first output's scene (backward compat) */ struct wl_listener backend_new_output_listener; + struct wl_listener output_layout_change_listener; char socket_name[64]; char host_wayland_display[64]; /* saved from env before overwrite */ int started; /* non-zero after successful display_open */ @@ -313,6 +316,7 @@ struct wl_compositor_state { struct wl_listener pointer_axis_listener; struct wl_listener real_pointer_axis_listener; struct wl_listener real_pointer_destroy_listener; + struct wl_list real_pointer_listeners; /* Phase 30: WM dock panel (native Wayland rendering via wm_shm_surface). */ struct wm_shm_surface *dock_surface; /* NULL until screen_create_internals */ int dock_width; /* pixels reserved on right edge */ diff --git a/src/backend/wayland/wl_xwayland.c b/src/backend/wayland/wl_xwayland.c index d53a763..41b77ec 100644 --- a/src/backend/wayland/wl_xwayland.c +++ b/src/backend/wayland/wl_xwayland.c @@ -358,8 +358,13 @@ handle_xwayland_new_surface(struct wl_listener *listener, void *data) wl_list_insert(&wl_state.toplevel_list, &view->link); wl_state.managed_surface_count++; - view->x = (wl_state.output ? (int)wl_state.output->width : 1920) / 4; - view->y = (wl_state.output ? (int)wl_state.output->height : 1080) / 4; + { + WScreen *scr = wl_state.scr; + int vw = (scr && scr->scr_width > 0) ? scr->scr_width : (wl_state.output ? (int)wl_state.output->width : 1920); + int vh = (scr && scr->scr_height > 0) ? scr->scr_height : (wl_state.output ? (int)wl_state.output->height : 1080); + view->x = vw / 4; + view->y = vh / 4; + } if (xw->surface) { view->xw_surface_map_listener.notify = handle_xwayland_surface_map; diff --git a/src/dock.c b/src/dock.c index d2ac171..52b04f3 100644 --- a/src/dock.c +++ b/src/dock.c @@ -747,27 +747,26 @@ static void unhideHereCallback(WMenu *menu, WMenuEntry *entry) wUnhideApplication(wapp, False, True); } +static int getDockXPositionForHead(WScreen *scr, int head, Bool on_right_side) +{ + WMRect rect; + + rect = wGetRectForHead(scr, head); + if (on_right_side) + return rect.pos.x + rect.size.width - ICON_SIZE - DOCK_EXTRA_SPACE; + return rect.pos.x + DOCK_EXTRA_SPACE; +} + static int getDockXPosition(WScreen *scr, Bool on_right_side) { - int x; + int head; - if (wPreferences.keep_dock_on_primary_head) { - WMRect rect; + if (wPreferences.keep_dock_on_primary_head) + head = scr->primary_head; + else + head = wGetHeadForPointerLocation(scr); - rect = wGetRectForHead(scr, scr->primary_head); - x = rect.pos.x; - if (on_right_side) - x += rect.size.width - ICON_SIZE - DOCK_EXTRA_SPACE; - else - x += DOCK_EXTRA_SPACE; - } else { - if (on_right_side) - x = scr->scr_width - ICON_SIZE - DOCK_EXTRA_SPACE; - else - x = DOCK_EXTRA_SPACE; - } - - return x; + return getDockXPositionForHead(scr, head, on_right_side); } /* Name is only used when type == WM_DRAWER and when restoring a specific @@ -1727,26 +1726,30 @@ WDock *wDockRestoreState(WScreen *scr, WMPropList *dock_state, int type) wScreenKeepInside(scr, &x, &dock->y_pos, ICON_SIZE, ICON_SIZE); } - /* Is this needed any more? */ - if (type == WM_CLIP) { - if (dock->x_pos < 0) { - dock->x_pos = 0; - } else if (dock->x_pos > scr->scr_width - ICON_SIZE) { - dock->x_pos = scr->scr_width - ICON_SIZE; - } - } else { - int left, right, midpoint; - - left = getDockXPosition(scr, False); - right = getDockXPosition(scr, True); - midpoint = (left + right) / 2; - - if (dock->x_pos < midpoint) { - dock->x_pos = left; - dock->on_right_side = 0; + /* Clamp restore positions relative to the head they land on. */ + { + int head = wGetHeadForPoint(scr, wmkpoint(dock->x_pos, dock->y_pos)); + WMRect rect = wGetRectForHead(scr, head); + if (type == WM_CLIP) { + if (dock->x_pos < rect.pos.x) { + dock->x_pos = rect.pos.x; + } else if (dock->x_pos > rect.pos.x + rect.size.width - ICON_SIZE) { + dock->x_pos = rect.pos.x + rect.size.width - ICON_SIZE; + } } else { - dock->x_pos = right; - dock->on_right_side = 1; + int left, right, midpoint; + + left = getDockXPositionForHead(scr, head, False); + right = getDockXPositionForHead(scr, head, True); + midpoint = (left + right) / 2; + + if (dock->x_pos < midpoint) { + dock->x_pos = left; + dock->on_right_side = 0; + } else { + dock->x_pos = right; + dock->on_right_side = 1; + } } } }