Compare commits

...

6 commits

Author SHA1 Message Date
EBADBEEF
6d38da24b3 Merge branch 'wlr_output_group' into 'master'
Draft: output: introduce wlr_output_group

See merge request wlroots/wlroots!4154
2025-10-23 10:46:22 +00:00
David Turner
879243e370 xwm: Fix double-close
When an FD is passed to xcb_connect_to_fd(), xcb takes ownership of that
FD and is responsible for closing it, which it does when
xcb_disconnect() is called.  But the xwayland handler code also keeps a
copy of the FD and closes it via safe_close() in
server_finish_process().

This double-close can cause all sorts of problems if another part of
wlroots allocates another FD between the two closes - the latter close
will close the wrong FD and things go horribly wrong (in my case leading
to use-after-free and segfaults).

Fix this by setting wm_fd[0]=-1 after calling xwm_create(), and ensuring
that xwm_create() closes the FD if startup errors occur.
2025-10-20 14:02:29 +01:00
EBADBEEF
e9124b8a9a backend/drm: automatically use wlr_output_group for tiled outputs 2025-08-21 09:38:01 -07:00
EBADBEEF
2a697512d7 backend/drm: parse TILE property 2025-08-21 09:38:01 -07:00
EBADBEEF
cbf921ccbb output: introduce wlr_output_group
An output group is an output implementation that contains one or more
separate outputs as children. The children can be grouped together to
turn into one big output (for a tiled display).

Updates:
- use upstream src_box/dst_box
- provide wlr_backend_impl, set features.timeline=true if all children
  have it
- fix output destroy (happens on vt switch) by properly removing output
  from group registry
- add output->impl->test because otherwise trying direct scan out can
  fail and stop updating the screen (scene_entry_try_direct_scanout)
- use wlr_output_finish() on destroy
- remove output->impl->get_buffer_caps (but keep the logic that
  buffer_caps is the intersection of all children)
2025-08-21 09:38:01 -07:00
EBADBEEF
a1ddc25b45 output: expose output_apply_state() to header
- need a way to apply the work of commit without side-effects
- related to 8f67446cc7 and f042de3f51
2025-08-21 09:36:44 -07:00
14 changed files with 808 additions and 5 deletions

View file

@ -15,6 +15,7 @@
#include <wlr/interfaces/wlr_output.h>
#include <wlr/render/drm_syncobj.h>
#include <wlr/render/wlr_renderer.h>
#include <wlr/types/wlr_output_group.h>
#include <wlr/util/box.h>
#include <wlr/util/log.h>
#include <wlr/util/transform.h>
@ -1724,6 +1725,12 @@ static bool connect_drm_connector(struct wlr_drm_connector *wlr_conn,
}
free(edid);
size_t tile_len = 0;
uint8_t *tile = get_drm_prop_blob(drm->fd,
wlr_conn->id, wlr_conn->props.tile, &tile_len);
parse_tile(wlr_conn, tile_len, tile);
free(tile);
char *subconnector = NULL;
if (wlr_conn->props.subconnector) {
subconnector = get_drm_prop_enum(drm->fd,
@ -1870,10 +1877,20 @@ void scan_drm_connectors(struct wlr_drm_backend *drm,
for (size_t i = 0; i < new_outputs_len; ++i) {
struct wlr_drm_connector *conn = new_outputs[i];
wlr_drm_conn_log(conn, WLR_INFO, "Requesting modeset");
wl_signal_emit_mutable(&drm->backend.events.new_output,
&conn->output);
if(conn->tile_info.group_id) {
struct wlr_output_group *group = wlr_output_group_match_tile(&conn->tile_info);
if (group) {
wlr_drm_conn_log(conn, WLR_INFO, "Adding %s to existing group", conn->name);
} else {
wlr_drm_conn_log(conn, WLR_INFO, "Creating output group for %s", conn->name);
group = wlr_output_group_create();
}
wlr_output_group_add_tile(group, &conn->output, &conn->tile_info);
} else {
wlr_drm_conn_log(conn, WLR_INFO, "Requesting modeset");
wl_signal_emit_mutable(&drm->backend.events.new_output,
&conn->output);
}
}
}

View file

@ -27,6 +27,7 @@ static const struct prop_info connector_info[] = {
{ "EDID", INDEX(edid) },
{ "HDR_OUTPUT_METADATA", INDEX(hdr_output_metadata) },
{ "PATH", INDEX(path) },
{ "TILE", INDEX(tile) },
{ "content type", INDEX(content_type) },
{ "link-status", INDEX(link_status) },
{ "max bpc", INDEX(max_bpc) },

View file

@ -97,6 +97,47 @@ void parse_edid(struct wlr_drm_connector *conn, size_t len, const uint8_t *data)
di_info_destroy(info);
}
void parse_tile(struct wlr_drm_connector *conn, size_t len, const uint8_t *data) {
struct wlr_output_group_tile_info *tile_info = &conn->tile_info;
memset(tile_info, 0, sizeof(*tile_info));
if (len == 0)
return;
// Reference:
// - include/linux/drm/drm_connector.h tile_blob_ptr
// - drivers/gpu/drm/drm_edid.c drm_parse_tiled_block()
//
// Note: group_id is always > 0
int ret = sscanf((char*)data, "%d:%d:%d:%d:%d:%d:%d:%d",
&tile_info->group_id,
&tile_info->is_single_monitor,
&tile_info->num_h,
&tile_info->num_v,
&tile_info->h_loc,
&tile_info->v_loc,
&tile_info->h_size,
&tile_info->v_size);
if(ret != 8) {
wlr_log(WLR_ERROR, "Unable to understand tile information for "
"connector %s", conn->name);
return;
}
wlr_log(WLR_INFO, "Connector '%s' TILE information: "
"group ID %d, single monitor %d, total %d horizontal tiles, "
"total %d vertical tiles, horizontal tile %d, vertical tile "
"%d, width %d, height %d",
conn->name,
tile_info->group_id,
tile_info->is_single_monitor,
tile_info->num_h,
tile_info->num_v,
tile_info->h_loc,
tile_info->v_loc,
tile_info->h_size,
tile_info->v_size);
}
const char *drm_connector_status_str(drmModeConnection status) {
switch (status) {
case DRM_MODE_CONNECTED:

View file

@ -10,6 +10,7 @@
#include <wlr/backend/session.h>
#include <wlr/render/drm_format_set.h>
#include <wlr/types/wlr_output_layer.h>
#include <wlr/types/wlr_output_group.h>
#include <xf86drmMode.h>
#include "backend/drm/iface.h"
#include "backend/drm/properties.h"
@ -219,6 +220,8 @@ struct wlr_drm_connector {
uint32_t hdr_output_metadata;
int32_t refresh;
struct wlr_output_group_tile_info tile_info;
};
struct wlr_drm_backend *get_drm_backend_from_backend(

View file

@ -22,6 +22,7 @@ struct wlr_drm_connector_props {
uint32_t panel_orientation; // not guaranteed to exist
uint32_t content_type; // not guaranteed to exist
uint32_t max_bpc; // not guaranteed to exist
uint32_t tile; // not guaranteed to exist
// atomic-modesetting only

View file

@ -14,6 +14,7 @@ enum wlr_output_mode_aspect_ratio get_picture_aspect_ratio(const drmModeModeInfo
const char *get_pnp_manufacturer(const char code[static 3]);
// Populates the make/model/phys_{width,height} of output from the edid data
void parse_edid(struct wlr_drm_connector *conn, size_t len, const uint8_t *data);
void parse_tile(struct wlr_drm_connector *conn, size_t len, const uint8_t *data);
const char *drm_connector_status_str(drmModeConnection status);
void generate_cvt_mode(drmModeModeInfo *mode, int hdisplay, int vdisplay,
float vrefresh);

View file

@ -28,6 +28,7 @@ void output_defer_present(struct wlr_output *output, struct wlr_output_event_pre
bool output_prepare_commit(struct wlr_output *output, const struct wlr_output_state *state);
void output_apply_commit(struct wlr_output *output, const struct wlr_output_state *state);
void output_send_commit_event(struct wlr_output *output, const struct wlr_output_state *state);
void output_apply_state(struct wlr_output *output, const struct wlr_output_state *state);
void output_state_get_buffer_src_box(const struct wlr_output_state *state,
struct wlr_fbox *out);

View file

@ -0,0 +1,63 @@
#ifndef WLR_USE_UNSTABLE
#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features"
#endif
#ifndef WLR_TYPES_WLR_OUTPUT_GROUP_H
#define WLR_TYPES_WLR_OUTPUT_GROUP_H
#include <stdint.h>
#include <wlr/types/wlr_output_group.h>
#include <wlr/types/wlr_output.h>
#include <wlr/render/drm_format_set.h>
#include <wlr/util/box.h>
#include <wlr/backend.h>
struct wlr_output_group_tile_info {
uint32_t group_id;
uint32_t is_single_monitor;
uint32_t num_h;
uint32_t num_v;
uint32_t h_loc;
uint32_t v_loc;
uint32_t h_size;
uint32_t v_size;
};
struct wlr_output_group_child {
struct wlr_output *output;
struct wlr_output_group *group;
struct wlr_fbox src_box;
struct wlr_box dst_box;
struct wlr_output_group_tile_info tile_info;
uint32_t index;
struct wlr_output_mode *tiled_mode;
struct wl_listener present;
struct wl_listener frame;
struct wl_listener needs_frame;
struct wl_listener output_destroy;
struct wl_list link;
};
struct wlr_output_group {
struct wlr_output output;
/* private data below */
int queued_frame_count;
int num_children;
struct wlr_output_mode *tiled_mode;
struct wl_list children; //wlr_output_group_child.link
struct wl_list mirrors; //wlr_output_group_child.link
struct wlr_drm_format_set cursor_formats;
struct wlr_drm_format_set primary_formats;
struct wl_event_source *ready;
struct wl_list link;
struct wlr_backend backend;
struct wlr_output_cursor_size *cursor_sizes;
size_t cursor_sizes_len;
};
struct wlr_output_group *wlr_output_group_create(void);
struct wlr_output_group *wlr_output_group_match_tile(struct wlr_output_group_tile_info *tile_info);
void wlr_output_group_add_tile(struct wlr_output_group *group, struct wlr_output *output,
struct wlr_output_group_tile_info *tile_info);
#endif

View file

@ -164,6 +164,7 @@ struct wlr_xwm {
struct wl_listener drop_focus_destroy;
};
// xwm_create takes ownership of wm_fd and will close it under all circumstances.
struct wlr_xwm *xwm_create(struct wlr_xwayland *wlr_xwayland, int wm_fd);
void xwm_destroy(struct wlr_xwm *xwm);

View file

@ -67,6 +67,7 @@ wlr_files += files(
'wlr_linux_drm_syncobj_v1.c',
'wlr_output_layer.c',
'wlr_output_layout.c',
'wlr_output_group.c',
'wlr_output_management_v1.c',
'wlr_output_power_management_v1.c',
'wlr_output_swapchain_manager.c',

View file

@ -210,7 +210,7 @@ void wlr_output_set_description(struct wlr_output *output, const char *desc) {
wl_signal_emit_mutable(&output->events.description, output);
}
static void output_apply_state(struct wlr_output *output,
void output_apply_state(struct wlr_output *output,
const struct wlr_output_state *state) {
if (state->committed & WLR_OUTPUT_STATE_RENDER_FORMAT) {
output->render_format = state->render_format;

667
types/wlr_output_group.c Normal file
View file

@ -0,0 +1,667 @@
#define _POSIX_C_SOURCE 200809L
#define _GNU_SOURCE
#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <wayland-util.h>
#include <wlr/backend/interface.h>
#include <wlr/interfaces/wlr_output.h>
#include <wlr/types/wlr_output_group.h>
#include <wlr/util/log.h>
#include <wlr/util/box.h>
#include <wlr/util/transform.h>
#include "backend/drm/drm.h"
#include "render/drm_format_set.h"
#include "types/wlr_output.h"
static const struct wlr_output_impl output_impl;
static const struct wlr_backend_impl backend_impl;
static void output_group_child_destroy(struct wlr_output_group_child *child);
static void output_group_state_change(void *data);
// A global registry for output groups.
static struct wl_list priv_registry;
static struct wl_list *registry = NULL;
static bool backend_is_group(struct wlr_backend *b) {
return (b->impl == &backend_impl);
}
static struct wlr_output_group *group_from_output(struct wlr_output *output) {
assert(output->impl == &output_impl);
return (struct wlr_output_group *)output;
}
static struct wlr_output_group *group_from_backend(struct wlr_backend *wlr_backend) {
assert(backend_is_group(wlr_backend));
struct wlr_output_group *group = wl_container_of(wlr_backend, group, backend);
return group;
}
static int backend_get_drm_fd(struct wlr_backend *backend) {
struct wlr_output_group *group = group_from_backend(backend);
struct wlr_output_group_child *primary =
wl_container_of(group->children.next, primary, link);
struct wlr_backend *child_backend = primary->output->backend;
if (child_backend->impl->get_drm_fd)
return child_backend->impl->get_drm_fd(child_backend);
return -1;
}
static void handle_present(struct wl_listener *listener, void *user_data) {
struct wlr_output_event_present *event = (struct wlr_output_event_present *) user_data;
struct wlr_output_group_child *child = wl_container_of(listener, child, present);
struct wlr_output_group *group = child->group;
if (group->queued_frame_count > 0) {
group->queued_frame_count -= 1;
}
if (group->queued_frame_count == 0) {
wlr_output_send_present(&group->output, event);
}
}
static void handle_frame(struct wl_listener *listener, void *user_data) {
struct wlr_output_group_child *child = wl_container_of(listener, child, frame);
struct wlr_output_group *group = child->group;
/* present happens before frame so trust that backend already did present */
if (group->queued_frame_count == 0) {
wl_signal_emit_mutable(&group->output.events.frame, &group->output);
}
}
static void handle_needs_frame(struct wl_listener *listener, void *user_data) {
struct wlr_output *output = (struct wlr_output *) user_data;
struct wlr_output_group_child *child = wl_container_of(listener, child, needs_frame);
struct wlr_output_group *group = child->group;
/* if any output raises needs_frame, re-raise it */
output->needs_frame = false;
wlr_output_update_needs_frame(&group->output);
}
static void handle_output_destroy(struct wl_listener *listener, void *user_data) {
struct wlr_output_group_child *child = wl_container_of(listener, child, output_destroy);
output_group_child_destroy(child);
}
struct wlr_output_group_mode {
struct wlr_output_mode mode;
struct wlr_output_mode *original_mode;
};
#define GROUP_PREFIX "GROUP-"
static bool clone_output(struct wlr_output_group *group, struct wlr_output *src_output,
struct wlr_output_group_tile_info *tile_info) {
struct wlr_output *dst_output = &group->output;
size_t new_name_len = sizeof(GROUP_PREFIX) + strlen(src_output->name);
char *new_name = malloc(new_name_len);
if (!new_name)
return false;
snprintf(new_name, new_name_len, "%s%s", GROUP_PREFIX, src_output->name);
wlr_output_init(dst_output, &group->backend, &output_impl, src_output->event_loop, NULL);
wlr_output_set_description(dst_output, src_output->description);
dst_output->name = new_name;
dst_output->make = strdup(src_output->make);
dst_output->model = strdup(src_output->model);
dst_output->serial = strdup(src_output->serial);
dst_output->phys_width = src_output->phys_width;
dst_output->phys_height = src_output->phys_height;
dst_output->current_mode = src_output->current_mode;
dst_output->width = src_output->width;
dst_output->height = src_output->height;
dst_output->refresh = src_output->refresh;
dst_output->enabled = src_output->enabled;
dst_output->scale = src_output->scale;
dst_output->subpixel = src_output->subpixel;
dst_output->transform = src_output->transform;
dst_output->adaptive_sync_status = src_output->adaptive_sync_status;
/* Clone all child modes while but keep references to the original. This is
* needed because the drm backend uses its own mode container
* (wlr_drm_mode) to link a wlr_mode to a drmModeModeInfo. */
struct wlr_output_mode *mode;
wl_list_for_each_reverse(mode, &src_output->modes, link) {
struct wlr_output_group_mode *group_mode = calloc(1, sizeof(*group_mode));
memcpy(&group_mode->mode, mode, sizeof(*mode));
group_mode->original_mode = mode;
wl_list_insert(&dst_output->modes, &group_mode->mode.link);
}
return true;
}
struct wlr_output_group *wlr_output_group_match_tile(struct wlr_output_group_tile_info *tile_info) {
if (!registry) {
return NULL;
}
struct wlr_output_group *group;
wl_list_for_each(group, registry, link) {
struct wlr_output_group_child *child = wl_container_of(group->children.next, child, link);
if (child->tile_info.group_id == tile_info->group_id) {
return group;
}
}
return NULL;
}
void wlr_output_group_add_tile(struct wlr_output_group *group, struct wlr_output *output,
struct wlr_output_group_tile_info *tile_info) {
struct wlr_output_group_child *child = calloc(1, sizeof(*child));
assert(tile_info->group_id != 0);
child->output = output;
child->output_destroy.notify = handle_output_destroy;
wl_signal_add(&output->events.destroy, &child->output_destroy);
child->frame.notify = handle_frame;
wl_signal_add(&output->events.frame, &child->frame);
child->present.notify = handle_present;
wl_signal_add(&output->events.present, &child->present);
child->needs_frame.notify = handle_needs_frame;
wl_signal_add(&output->events.needs_frame, &child->needs_frame);
child->group = group;
child->tile_info = *tile_info;
/* index is like array v,h:
* #1 [0,0], #2 [0,1], #3 [0,2],
* #4 [1,0], #5 [1,1], #6 [1,2],
* #7 [2,0], #8 [2,1], #9 [2,2],
*/
child->index = (tile_info->v_loc * tile_info->num_h) + tile_info->h_loc;
/* sorted insert to keep children in order for calculating tiled mode */
struct wlr_output_group_child *cur;
wl_list_for_each(cur, &group->children, link) {
if (child->index < cur->index) {
break;
}
}
wl_list_insert(cur->link.prev, &child->link);
if (child->output->backend->features.timeline == false) {
group->backend.features.timeline = false;
}
group->backend.buffer_caps &= child->output->backend->buffer_caps;
if (group->ready == NULL) {
group->ready = wl_event_loop_add_idle(child->output->event_loop,
output_group_state_change, group);
}
}
struct wlr_output_group *wlr_output_group_create(void) {
if (registry == NULL) {
wl_list_init(&priv_registry);
registry = &priv_registry;
}
struct wlr_output_group *group = calloc(1, sizeof(*group));
wl_list_insert(registry, &group->link);
wl_list_init(&group->children);
wlr_backend_init(&group->backend, &backend_impl);
group->backend.features.timeline = true;
group->backend.buffer_caps = ~0;
return group;
}
static void output_group_destroy(struct wlr_output *output) {
struct wlr_output_group *group = group_from_output(output);
struct wlr_output_group_child *child,*child_tmp;
wlr_output_finish(output);
wl_list_for_each_safe(child, child_tmp, &group->children, link) {
output_group_child_destroy(child);
}
struct wlr_output_group_mode *mode, *mode_tmp;
wl_list_for_each_safe(mode, mode_tmp, &group->output.modes, mode.link) {
wl_list_remove(&mode->mode.link);
free(mode);
}
if (group->ready != NULL) {
wl_event_source_remove(group->ready);
}
free(group->cursor_sizes);
wl_list_remove(&group->link);
free(group);
}
static void output_group_child_destroy(struct wlr_output_group_child *child) {
struct wlr_output_group *group = child->group;
wlr_log(WLR_DEBUG, "removing child %s from group %s",
child->output->name, group->output.name);
wl_list_remove(&child->present.link);
wl_list_remove(&child->needs_frame.link);
wl_list_remove(&child->frame.link);
wl_list_remove(&child->output_destroy.link);
wl_list_remove(&child->link);
/* Schedule a group state change event. When all children are removed, the
* output will be destroyed. */
if (group->ready == NULL) {
group->ready = wl_event_loop_add_idle(child->output->event_loop,
output_group_state_change, group);
}
free(child);
}
static bool output_group_commit_helper(struct wlr_output *parent, const struct wlr_output_state *state, bool test) {
struct wlr_output_group *group = group_from_output(parent);
bool ret = false;
bool failed = false;
bool in_tiled_mode = false;
if(state->committed & WLR_OUTPUT_STATE_MODE) {
if (state->mode == group->tiled_mode) {
in_tiled_mode = true;
}
} else {
if (parent->current_mode == group->tiled_mode) {
in_tiled_mode = true;
}
}
/* TODO: it is probably possible to figure out crop/scaling for children */
if (state->committed & WLR_OUTPUT_STATE_BUFFER) {
if (state->buffer_dst_box.x != 0 || state->buffer_dst_box.y != 0 ||
state->buffer_src_box.x != 0.0 || state->buffer_src_box.y != 0.0) {
wlr_log(WLR_DEBUG, "crop/scaling not implemented in output group");
return false;
}
}
struct wlr_output_group_child *child;
bool single_output_enabled = false;
int frame_count = 0;
wl_list_for_each(child, &group->children, link) {
struct wlr_output *output = child->output;
struct wlr_output_state state_copy = *state;
struct wlr_output_state *pending = &state_copy;
/* commit_seq important for presentation feedback! */
output->commit_seq = parent->commit_seq;
if (in_tiled_mode) {
frame_count += 1;
pending->buffer_src_box = child->src_box;
pending->buffer_dst_box = child->dst_box;
pending->mode = child->tiled_mode;
if (output->enabled == false && !(pending->committed & WLR_OUTPUT_STATE_ENABLED)) {
pending->committed |= WLR_OUTPUT_STATE_ENABLED;
pending->enabled = true;
}
} else {
frame_count = 1;
if (output->enabled == true || (pending->committed & WLR_OUTPUT_STATE_ENABLED && pending->enabled == true)) {
if (single_output_enabled == false) {
/* first child gets turned on */
if ((pending->committed & WLR_OUTPUT_STATE_MODE) && (pending->mode_type == WLR_OUTPUT_STATE_MODE_FIXED)) {
struct wlr_output_group_mode *group_mode = wl_container_of(pending->mode, group_mode, mode);
pending->mode = group_mode->original_mode;
}
single_output_enabled = true;
} else {
/* rest of the children get forced off */
pending->committed = WLR_OUTPUT_STATE_ENABLED;
pending->enabled = false;
}
}
}
if (output->enabled == false && !(pending->committed & WLR_OUTPUT_STATE_ENABLED)) {
continue;
}
/* The parent group output is going through wlr_output_commit_state()
* and managing state. Skip directly to the children output
* implementation commit. */
if (test) {
if (output->impl->test) {
if (false == output->impl->test(output, pending)) {
return false;
}
}
} else {
ret = output->impl->commit(output, pending);
if(ret == false) {
failed = true;
wlr_log(WLR_DEBUG, "commit failed on %s", output->name);
}
if (!failed) {
output_apply_state(output, pending);
if (output->frame_pending) {
parent->frame_pending = true;
}
}
}
}
if (test) {
return true;
} else if (failed) {
/* Do not present any frame where any children failed */
group->queued_frame_count = -1;
} else {
/* Synchronize all children outputs to prevent tearing. Make
* sure we get all the children frame/present events before
* forwarding that to the group output. */
group->queued_frame_count = frame_count;
}
return !failed;
}
static bool output_group_commit(struct wlr_output *parent, const struct wlr_output_state *state) {
return output_group_commit_helper(parent, state, false);
}
static bool output_group_test(struct wlr_output *parent, const struct wlr_output_state *state) {
return output_group_commit_helper(parent, state, true);
}
static size_t output_group_get_gamma_size(struct wlr_output *output) {
struct wlr_output_group *group = group_from_output(output);
size_t gamma_size = 0;
size_t tmp_gamma_size = 0;
struct wlr_output_group_child *child;
wl_list_for_each(child, &group->children, link) {
if (child->output->impl->get_gamma_size) {
tmp_gamma_size = child->output->impl->get_gamma_size(child->output);
}
if (gamma_size == 0) {
gamma_size = tmp_gamma_size;
}
if (tmp_gamma_size == 0 || tmp_gamma_size != gamma_size) {
return 0;
}
}
return gamma_size;
}
static bool output_group_set_cursor(struct wlr_output *output,
struct wlr_buffer *buffer, int hotspot_x, int hotspot_y) {
struct wlr_output_group *group = group_from_output(output);
struct wlr_output_group_child *child;
wl_list_for_each(child, &group->children, link) {
if (child->output->enabled) {
child->output->impl->set_cursor(child->output, buffer, hotspot_x, hotspot_y);
}
};
return true;
}
static bool output_group_move_cursor(struct wlr_output *output,
int x, int y) {
struct wlr_output_group *group = group_from_output(output);
struct wlr_output *parent = &group->output;
struct wlr_output_group_child *child;
/* copied from backend/drm.c ;-) */
struct wlr_box box = { .x = x, .y = y };
int width, height;
enum wl_output_transform transform = wlr_output_transform_invert(parent->transform);
wlr_output_transformed_resolution(output, &width, &height);
wlr_box_transform(&box, &box, transform, width, height);
wl_list_for_each(child, &group->children, link) {
if (child->output->enabled) {
child->output->impl->move_cursor(child->output, box.x - child->src_box.x, box.y - child->src_box.y);
}
};
return true;
}
static const struct wlr_output_cursor_size *output_group_get_cursor_sizes(struct wlr_output *output, size_t *len) {
struct wlr_output_group *group = group_from_output(output);
*len = group->cursor_sizes_len;
return group->cursor_sizes;
}
static const struct wlr_drm_format_set *output_group_get_cursor_formats(
struct wlr_output *output, uint32_t buffer_caps) {
struct wlr_output_group *group = group_from_output(output);
struct wlr_output_group_child *child;
bool first = true;
wl_list_for_each(child, &group->children, link) {
if (!child->output->impl->get_cursor_formats) {
wlr_drm_format_set_finish(&group->cursor_formats);
break;
}
const struct wlr_drm_format_set *set =
child->output->impl->get_cursor_formats(child->output, buffer_caps);
if (first) {
wlr_drm_format_set_copy(&group->cursor_formats, set);
first = false;
} else {
wlr_drm_format_set_intersect(&group->cursor_formats, &group->cursor_formats, set);
}
}
return &group->cursor_formats;
}
static const struct wlr_drm_format_set *output_group_get_primary_formats(
struct wlr_output *output, uint32_t buffer_caps) {
struct wlr_output_group *group = group_from_output(output);
bool first = true;
struct wlr_output_group_child *child;
wl_list_for_each(child, &group->children, link) {
if (!child->output->impl->get_primary_formats) {
wlr_drm_format_set_finish(&group->primary_formats);
break;
}
const struct wlr_drm_format_set *set =
child->output->impl->get_primary_formats(child->output, buffer_caps);
if (first) {
wlr_drm_format_set_copy(&group->primary_formats, set);
first = false;
} else {
wlr_drm_format_set_intersect(&group->primary_formats, &group->primary_formats, set);
}
}
return &group->primary_formats;
}
static void calculate_and_allocate_tiled_mode(struct wlr_output_group *group) {
struct wlr_output_group_mode *group_mode = calloc(1, sizeof(*group_mode));
uint32_t x_start = 0, y_start = 0;
struct wlr_output_group_child *child;
wl_list_for_each(child, &group->children, link) {
struct wlr_output_group_tile_info *tile_info = &child->tile_info;
/* this depends on iterating through the children in tile index order and
* assumes the dimensions work */
if (tile_info->v_loc == 0) {
group_mode->mode.width += tile_info->h_size;
}
if (tile_info->h_loc == 0) {
group_mode->mode.height += tile_info->v_size;
}
/* Generate the crop for this specific tile. The source buffer is shared
* between all tiles and each child output takes a subset of the shared
* buffer. */
child->src_box.x = x_start;
child->src_box.y = y_start;
child->src_box.width = tile_info->h_size;
child->src_box.height = tile_info->v_size;
child->dst_box.x = 0;
child->dst_box.y = 0;
child->dst_box.width = tile_info->h_size;
child->dst_box.height = tile_info->v_size;
if (tile_info->h_loc == (tile_info->num_h-1)) {
x_start = 0;
y_start += tile_info->v_size;
} else {
x_start += tile_info->h_size;
}
struct wlr_output_mode *mode;
wl_list_for_each(mode, &child->output->modes, link) {
if (mode->width == (int32_t)tile_info->h_size && mode->height == (int32_t)tile_info->v_size) {
child->tiled_mode = mode;
if ((group_mode->mode.refresh == 0) || (mode->refresh < group_mode->mode.refresh)) {
/* slowest refresh wins */
group_mode->mode.refresh = mode->refresh;
}
break;
}
}
}
//TODO: set aspect ratio?
group_mode->mode.picture_aspect_ratio = WLR_OUTPUT_MODE_ASPECT_RATIO_NONE;
group_mode->mode.preferred = true;
group->tiled_mode = &group_mode->mode;
wl_list_insert(&group->output.modes, &group_mode->mode.link);
}
static bool is_cursor_size_in_all_children(struct wlr_output_group *group, struct wlr_output_group_child *myself, const struct wlr_output_cursor_size *size) {
struct wlr_output_group_child *child;
wl_list_for_each(child, &group->children, link) {
size_t len = 0;
if (child == myself) {
continue;
}
if (!child->output->impl->get_cursor_sizes) {
continue;
}
const struct wlr_output_cursor_size *sizes = child->output->impl->get_cursor_sizes(child->output, &len);
bool found = false;
for (size_t i=0; i < len && found == false; ++i) {
if (sizes[i].width == size->width && sizes[i].height == size->height) {
found = true;
}
};
if (!found) {
return false;
}
}
return true;
}
static void allocate_cursor_sizes(struct wlr_output_group *group) {
struct wlr_output_group_child *child;
size_t cur_idx = 0;
/* Save the intersection of hardware cursor sizes. */
wl_list_for_each(child, &group->children, link) {
size_t len = 0;
if (!child->output->impl->get_cursor_sizes) {
continue;
}
const struct wlr_output_cursor_size *sizes = child->output->impl->get_cursor_sizes(child->output, &len);
group->cursor_sizes = calloc(len, sizeof(struct wlr_output_cursor_size));
for (size_t i=0; i<len; ++i) {
if (is_cursor_size_in_all_children(group, child, &sizes[i])) {
group->cursor_sizes[cur_idx] = sizes[i];
cur_idx += 1;
}
}
group->cursor_sizes_len = cur_idx;
break;
}
}
static void output_group_state_change(void *data) {
struct wlr_output_group *group = data;
int num_children = wl_list_length(&group->children);
bool need_init = false;
bool need_destroy = false;
if (group->num_children > 0) {
need_destroy = true;
}
if (num_children > 0) {
need_init = true;
}
if (need_destroy) {
struct wlr_output_group *old_group = group;
if (need_init) {
struct wlr_output_group *new_group = wlr_output_group_create();
struct wlr_output_group_child *child, *child_tmp;
/* prevent re-entering */
new_group->ready = old_group->ready;
/* disable old group */
const struct wlr_output_state pending = (struct wlr_output_state) {
.committed = WLR_OUTPUT_STATE_ENABLED,
.allow_reconfiguration = true,
.enabled = false,
};
wlr_output_commit_state(&old_group->output, &pending);
/* move children to new group */
wl_list_for_each_safe(child, child_tmp, &group->children, link) {
wlr_output_group_add_tile(new_group, child->output, &child->tile_info);
output_group_child_destroy(child);
}
/* old group will get free'd during output destroy */
group = new_group;
}
wlr_output_destroy(&old_group->output);
}
group->ready = NULL;
group->num_children = num_children;
if (!need_init) {
return;
}
/* the first child is the primary */
struct wlr_output_group_child *primary =
wl_container_of(group->children.next, primary, link);
if (!clone_output(group, primary->output, &primary->tile_info))
return;
/* try to support a partial tiled array */
int num_children_needed;
if (!primary->tile_info.is_single_monitor) {
num_children_needed = (primary->tile_info.num_h * primary->tile_info.num_v);
} else {
num_children_needed = 1;
}
if (num_children >= num_children_needed) {
calculate_and_allocate_tiled_mode(group);
}
allocate_cursor_sizes(group);
wlr_log(WLR_INFO, "created output group %s (%dx%d mm)",
group->output.name, group->output.phys_width, group->output.phys_height);
struct wlr_output_mode *mode;
wl_list_for_each(mode, &group->output.modes, link) {
wlr_log(WLR_DEBUG, " mode %dx%d@%d %s %s",
mode->width, mode->height, mode->refresh,
mode->preferred?"(preferred)":"",
(mode == group->tiled_mode)?"(tiled)":"");
}
wl_signal_emit_mutable(&primary->output->backend->events.new_output, &group->output);
}
static const struct wlr_output_impl output_impl = {
.destroy = output_group_destroy,
.test = output_group_test,
.commit = output_group_commit,
.get_gamma_size = output_group_get_gamma_size,
.set_cursor = output_group_set_cursor,
.move_cursor = output_group_move_cursor,
.get_cursor_formats = output_group_get_cursor_formats,
.get_cursor_sizes = output_group_get_cursor_sizes,
.get_primary_formats = output_group_get_primary_formats,
};
static const struct wlr_backend_impl backend_impl = {
.start = NULL,
.destroy = NULL,
.get_drm_fd = backend_get_drm_fd,
.test = NULL,
.commit = NULL,
};

View file

@ -42,6 +42,9 @@ static void handle_server_start(struct wl_listener *listener, void *data) {
static void xwayland_mark_ready(struct wlr_xwayland *xwayland) {
assert(xwayland->server->wm_fd[0] >= 0);
xwayland->xwm = xwm_create(xwayland, xwayland->server->wm_fd[0]);
// xwm_create takes ownership of wm_fd[0] under all circumstances
xwayland->server->wm_fd[0] = -1;
if (!xwayland->xwm) {
return;
}

View file

@ -2530,6 +2530,7 @@ void xwm_set_cursor(struct wlr_xwm *xwm, const uint8_t *pixels, uint32_t stride,
struct wlr_xwm *xwm_create(struct wlr_xwayland *xwayland, int wm_fd) {
struct wlr_xwm *xwm = calloc(1, sizeof(*xwm));
if (xwm == NULL) {
close(wm_fd);
return NULL;
}
@ -2544,11 +2545,13 @@ struct wlr_xwm *xwm_create(struct wlr_xwayland *xwayland, int wm_fd) {
xwm->ping_timeout = 10000;
// xcb_connect_to_fd takes ownership of the FD regardless of success/failure
xwm->xcb_conn = xcb_connect_to_fd(wm_fd, NULL);
int rc = xcb_connection_has_error(xwm->xcb_conn);
if (rc) {
wlr_log(WLR_ERROR, "xcb connect failed: %d", rc);
xcb_disconnect(xwm->xcb_conn);
free(xwm);
return NULL;
}