Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 30 additions & 29 deletions src/platform/linux/wayland.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,17 +54,17 @@ namespace wl {
}

if (!display_name) {
BOOST_LOG(error) << "Environment variable WAYLAND_DISPLAY has not been defined"sv;
BOOST_LOG(error) << "[wayland] Environment variable WAYLAND_DISPLAY has not been defined"sv;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this pattern. It would be nice if we could automatically include the source file and line number from where a log message was initiated. I don't know how hard it would be to implement in Sunshine, but I did something similar in our python discord bot recently.

2026-02-27 23:14:42 - src.common.database - [database.py:321] - INFO - Committed changes to git data repository
2026-02-27 23:14:43 - src.common.database - [database.py:329] - INFO - Pushed changes to remote git data repository
2026-02-27 23:18:58 - src.common.webapp - [webapp.py:297] - INFO - received webhook from github_status
2026-02-27 23:18:58 - src.common.webapp - [webapp.py:299] - INFO - received webhook data: 

In any event, it would be nice to have the capture method as a prefix for all the capture methods, whether on Linux, Windows, or macOS (even though there's only one on macOS right now).

return -1;
}

display_internal.reset(wl_display_connect(display_name));
if (!display_internal) {
BOOST_LOG(error) << "Couldn't connect to Wayland display: "sv << display_name;
BOOST_LOG(error) << "[wayland] Couldn't connect to Wayland display: "sv << display_name;
return -1;
}

BOOST_LOG(info) << "Found display ["sv << display_name << ']';
BOOST_LOG(info) << "[wayland] Found display ["sv << display_name << ']';

return 0;
}
Expand Down Expand Up @@ -127,26 +127,26 @@ namespace wl {
inline void monitor_t::xdg_name(zxdg_output_v1 *, const char *name) {
this->name = name;

BOOST_LOG(info) << "Name: "sv << this->name;
BOOST_LOG(info) << "[wayland] Name: "sv << this->name;
}

void monitor_t::xdg_description(zxdg_output_v1 *, const char *description) {
this->description = description;

BOOST_LOG(info) << "Found monitor: "sv << this->description;
BOOST_LOG(info) << "[wayland] Found monitor: "sv << this->description;
}

void monitor_t::xdg_position(zxdg_output_v1 *, std::int32_t x, std::int32_t y) {
viewport.offset_x = x;
viewport.offset_y = y;

BOOST_LOG(info) << "Offset: "sv << x << 'x' << y;
BOOST_LOG(info) << "[wayland] Offset: "sv << x << 'x' << y;
}

void monitor_t::xdg_size(zxdg_output_v1 *, std::int32_t width, std::int32_t height) {
viewport.logical_width = width;
viewport.logical_height = height;
BOOST_LOG(info) << "Logical size: "sv << width << 'x' << height;
BOOST_LOG(info) << "[wayland] Logical size: "sv << width << 'x' << height;
}

void monitor_t::wl_mode(
Expand All @@ -159,7 +159,7 @@ namespace wl {
viewport.width = width;
viewport.height = height;

BOOST_LOG(info) << "Resolution: "sv << width << 'x' << height;
BOOST_LOG(info) << "[wayland] Resolution: "sv << width << 'x' << height;
}

void monitor_t::listen(zxdg_output_manager_v1 *output_manager) {
Expand Down Expand Up @@ -189,35 +189,35 @@ namespace wl {
const char *interface,
std::uint32_t version
) {
BOOST_LOG(debug) << "Available interface: "sv << interface << '(' << id << ") version "sv << version;
BOOST_LOG(debug) << "[wayland] Available interface: "sv << interface << '(' << id << ") version "sv << version;

if (!std::strcmp(interface, wl_output_interface.name)) {
BOOST_LOG(info) << "Found interface: "sv << interface << '(' << id << ") version "sv << version;
BOOST_LOG(info) << "[wayland] Found interface: "sv << interface << '(' << id << ") version "sv << version;
monitors.emplace_back(
std::make_unique<monitor_t>(
(wl_output *) wl_registry_bind(registry, id, &wl_output_interface, 2)
)
);
} else if (!std::strcmp(interface, zxdg_output_manager_v1_interface.name)) {
BOOST_LOG(info) << "Found interface: "sv << interface << '(' << id << ") version "sv << version;
BOOST_LOG(info) << "[wayland] Found interface: "sv << interface << '(' << id << ") version "sv << version;
output_manager = (zxdg_output_manager_v1 *) wl_registry_bind(registry, id, &zxdg_output_manager_v1_interface, version);

this->interface[XDG_OUTPUT] = true;
} else if (!std::strcmp(interface, zwlr_screencopy_manager_v1_interface.name)) {
BOOST_LOG(info) << "Found interface: "sv << interface << '(' << id << ") version "sv << version;
BOOST_LOG(info) << "[wayland] Found interface: "sv << interface << '(' << id << ") version "sv << version;
screencopy_manager = (zwlr_screencopy_manager_v1 *) wl_registry_bind(registry, id, &zwlr_screencopy_manager_v1_interface, version);

this->interface[WLR_EXPORT_DMABUF] = true;
} else if (!std::strcmp(interface, zwp_linux_dmabuf_v1_interface.name)) {
BOOST_LOG(info) << "Found interface: "sv << interface << '(' << id << ") version "sv << version;
BOOST_LOG(info) << "[wayland] Found interface: "sv << interface << '(' << id << ") version "sv << version;
dmabuf_interface = (zwp_linux_dmabuf_v1 *) wl_registry_bind(registry, id, &zwp_linux_dmabuf_v1_interface, version);

this->interface[LINUX_DMABUF] = true;
}
}

void interface_t::del_interface(wl_registry *registry, uint32_t id) {
BOOST_LOG(info) << "Delete: "sv << id;
BOOST_LOG(info) << "[wayland] Delete: "sv << id;
}

// Initialize GBM
Expand All @@ -230,7 +230,7 @@ namespace wl {
drmDevice *devices[16];
int n = drmGetDevices2(0, devices, 16);
if (n <= 0) {
BOOST_LOG(error) << "No DRM devices found"sv;
BOOST_LOG(error) << "[wayland] No DRM devices found"sv;
return false;
}

Expand All @@ -246,14 +246,14 @@ namespace wl {
drmFreeDevices(devices, n);

if (drm_fd < 0) {
BOOST_LOG(error) << "Failed to open DRM render node"sv;
BOOST_LOG(error) << "[wayland] Failed to open DRM render node"sv;
return false;
}

gbm_device = gbm_create_device(drm_fd);
if (!gbm_device) {
close(drm_fd);
BOOST_LOG(error) << "Failed to create GBM device"sv;
BOOST_LOG(error) << "[wayland] Failed to create GBM device"sv;
return false;
}

Expand Down Expand Up @@ -343,8 +343,6 @@ namespace wl {
shm_info.width = width;
shm_info.height = height;
shm_info.stride = stride;

BOOST_LOG(debug) << "Screencopy supports SHM format: "sv << format;
}

// DMA-BUF format callback
Expand All @@ -358,14 +356,12 @@ namespace wl {
dmabuf_info.format = format;
dmabuf_info.width = width;
dmabuf_info.height = height;

BOOST_LOG(debug) << "Screencopy supports DMA-BUF format: "sv << format;
}

// Flags callback
void dmabuf_t::flags(zwlr_screencopy_frame_v1 *frame, std::uint32_t flags) {
y_invert = flags & ZWLR_SCREENCOPY_FRAME_V1_FLAGS_Y_INVERT;
BOOST_LOG(debug) << "Frame flags: "sv << flags << (y_invert ? " (y_invert)" : "");
BOOST_LOG(verbose) << "Frame flags: "sv << flags << (y_invert ? " (y_invert)" : "");
}

// DMA-BUF creation helper
Expand Down Expand Up @@ -433,11 +429,11 @@ namespace wl {
create_and_copy_dmabuf(frame);
} else if (shm_info.supported) {
// SHM fallback would go here
BOOST_LOG(warning) << "SHM capture not implemented"sv;
BOOST_LOG(warning) << "[wayland] SHM capture not implemented"sv;
zwlr_screencopy_frame_v1_destroy(frame);
status = REINIT;
} else {
BOOST_LOG(error) << "No supported buffer types"sv;
BOOST_LOG(error) << "[wayland] No supported buffer types"sv;
zwlr_screencopy_frame_v1_destroy(frame);
status = REINIT;
}
Expand Down Expand Up @@ -468,7 +464,7 @@ namespace wl {
auto frame = static_cast<zwlr_screencopy_frame_v1 *>(data);
auto self = static_cast<dmabuf_t *>(zwlr_screencopy_frame_v1_get_user_data(frame));

BOOST_LOG(error) << "Failed to create buffer from params"sv;
BOOST_LOG(error) << "[wayland] Failed to create buffer from params"sv;
self->cleanup_gbm();

zwp_linux_buffer_params_v1_destroy(params);
Expand All @@ -483,12 +479,16 @@ namespace wl {
std::uint32_t tv_sec_lo,
std::uint32_t tv_nsec
) {
BOOST_LOG(debug) << "Frame ready"sv;

// Frame is ready for use, GBM buffer now contains screen content
current_frame->destroy();
current_frame = get_next_frame();

std::uint64_t sec = (std::uint64_t(tv_sec_hi) << 32) | tv_sec_lo;
auto ready_ts = std::chrono::seconds(sec) + std::chrono::nanoseconds(tv_nsec);
current_frame->frame_timestamp = std::chrono::steady_clock::time_point{
std::chrono::duration_cast<std::chrono::steady_clock::duration>(ready_ts)
};

// Keep the GBM buffer alive but destroy the Wayland objects
if (current_wl_buffer) {
wl_buffer_destroy(current_wl_buffer);
Expand All @@ -503,7 +503,7 @@ namespace wl {

// Failed callback
void dmabuf_t::failed(zwlr_screencopy_frame_v1 *frame) {
BOOST_LOG(error) << "Frame capture failed"sv;
BOOST_LOG(error) << "[wayland] Frame capture failed"sv;

// Clean up resources
cleanup_gbm();
Expand All @@ -514,6 +514,7 @@ namespace wl {
status = REINIT;
}

// Only called if using zwlr_screencopy_frame_v1_copy_with_damage()
void dmabuf_t::damage(
zwlr_screencopy_frame_v1 *frame,
std::uint32_t x,
Expand Down Expand Up @@ -550,7 +551,7 @@ namespace wl {
display.roundtrip();

if (!interface[interface_t::XDG_OUTPUT]) {
BOOST_LOG(error) << "Missing Wayland wire XDG_OUTPUT"sv;
BOOST_LOG(error) << "[wayland] Missing Wayland wire XDG_OUTPUT"sv;
return {};
}

Expand Down
1 change: 1 addition & 0 deletions src/platform/linux/wayland.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ namespace wl {
void destroy();

egl::surface_descriptor_t sd;
std::optional<std::chrono::steady_clock::time_point> frame_timestamp;
};

class dmabuf_t {
Expand Down
50 changes: 32 additions & 18 deletions src/platform/linux/wlgrab.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,18 @@ namespace wl {
class wlr_t: public platf::display_t {
public:
int init(platf::mem_type_e hwdevice_type, const std::string &display_name, const ::video::config_t &config) {
delay = std::chrono::nanoseconds {1s} / config.framerate;
// calculate frame interval we should capture at
if (config.framerateX100 > 0) {
AVRational fps_strict = ::video::framerateX100_to_rational(config.framerateX100);
delay = std::chrono::nanoseconds(
(static_cast<int64_t>(fps_strict.den) * 1'000'000'000LL) / fps_strict.num
);
BOOST_LOG(info) << "[wlgrab] Requested frame rate [" << fps_strict.num << "/" << fps_strict.den << ", approx. " << av_q2d(fps_strict) << " fps]";
} else {
delay = std::chrono::nanoseconds {1s} / config.framerate;
BOOST_LOG(info) << "[wlgrab] Requested frame rate [" << config.framerate << "fps]";
}

mem_type = hwdevice_type;

if (display.init()) {
Expand All @@ -41,12 +52,12 @@ namespace wl {
display.roundtrip();

if (!interface[wl::interface_t::XDG_OUTPUT]) {
BOOST_LOG(error) << "Missing Wayland wire for xdg_output"sv;
BOOST_LOG(error) << "[wlgrab] Missing Wayland wire for xdg_output"sv;
return -1;
}

if (!interface[wl::interface_t::WLR_EXPORT_DMABUF]) {
BOOST_LOG(error) << "Missing Wayland wire for wlr-export-dmabuf"sv;
BOOST_LOG(error) << "[wlgrab] Missing Wayland wire for wlr-export-dmabuf"sv;
return -1;
}

Expand Down Expand Up @@ -88,12 +99,12 @@ namespace wl {
this->env_logical_width = desktop_logical_width;
this->env_logical_height = desktop_logical_height;

BOOST_LOG(info) << "Selected monitor ["sv << monitor->description << "] for streaming"sv;
BOOST_LOG(debug) << "Offset: "sv << offset_x << 'x' << offset_y;
BOOST_LOG(debug) << "Resolution: "sv << width << 'x' << height;
BOOST_LOG(debug) << "Logical Resolution: "sv << logical_width << 'x' << logical_height;
BOOST_LOG(debug) << "Desktop Resolution: "sv << env_width << 'x' << env_height;
BOOST_LOG(debug) << "Logical Desktop Resolution: "sv << env_logical_width << 'x' << env_logical_height;
BOOST_LOG(info) << "[wlgrab] Selected monitor ["sv << monitor->description << "] for streaming"sv;
BOOST_LOG(debug) << "[wlgrab] Offset: "sv << offset_x << 'x' << offset_y;
BOOST_LOG(debug) << "[wlgrab] Resolution: "sv << width << 'x' << height;
BOOST_LOG(debug) << "[wlgrab] Logical Resolution: "sv << logical_width << 'x' << logical_height;
BOOST_LOG(debug) << "[wlgrab] Desktop Resolution: "sv << env_width << 'x' << env_height;
BOOST_LOG(debug) << "[wlgrab] Logical Desktop Resolution: "sv << env_logical_width << 'x' << env_logical_height;

return 0;
}
Expand Down Expand Up @@ -177,7 +188,7 @@ namespace wl {
}
break;
default:
BOOST_LOG(error) << "Unrecognized capture status ["sv << (int) status << ']';
BOOST_LOG(error) << "[wlgrab] Unrecognized capture status ["sv << (int) status << ']';
return status;
}
}
Expand Down Expand Up @@ -209,11 +220,13 @@ namespace wl {
int w, h;
gl::ctx.GetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &w);
gl::ctx.GetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &h);
BOOST_LOG(debug) << "width and height: w "sv << w << " h "sv << h;
BOOST_LOG(debug) << "[wlgrab] width and height: w "sv << w << " h "sv << h;

gl::ctx.GetTextureSubImage((*rgb_opt)->tex[0], 0, 0, 0, 0, width, height, 1, GL_BGRA, GL_UNSIGNED_BYTE, img_out->height * img_out->row_pitch, img_out->data);
gl::ctx.BindTexture(GL_TEXTURE_2D, 0);

img_out->frame_timestamp = current_frame->frame_timestamp;

return platf::capture_e::ok;
}

Expand Down Expand Up @@ -307,7 +320,7 @@ namespace wl {
}
break;
default:
BOOST_LOG(error) << "Unrecognized capture status ["sv << (int) status << ']';
BOOST_LOG(error) << "[wlgrab] Unrecognized capture status ["sv << (int) status << ']';
return status;
}
}
Expand All @@ -333,6 +346,7 @@ namespace wl {
img->sequence = sequence;

img->sd = current_frame->sd;
img->frame_timestamp = current_frame->frame_timestamp;

// Prevent dmabuf from closing the file descriptors.
std::fill_n(current_frame->sd.fds, 4, -1);
Expand Down Expand Up @@ -384,7 +398,7 @@ namespace wl {
namespace platf {
std::shared_ptr<display_t> wl_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config) {
if (hwdevice_type != platf::mem_type_e::system && hwdevice_type != platf::mem_type_e::vaapi && hwdevice_type != platf::mem_type_e::cuda) {
BOOST_LOG(error) << "Could not initialize display with the given hw device type."sv;
BOOST_LOG(error) << "[wlgrab] Could not initialize display with the given hw device type."sv;
return nullptr;
}

Expand Down Expand Up @@ -419,12 +433,12 @@ namespace platf {
display.roundtrip();

if (!interface[wl::interface_t::XDG_OUTPUT]) {
BOOST_LOG(warning) << "Missing Wayland wire for xdg_output"sv;
BOOST_LOG(warning) << "[wlgrab] Missing Wayland wire for xdg_output"sv;
return {};
}

if (!interface[wl::interface_t::WLR_EXPORT_DMABUF]) {
BOOST_LOG(warning) << "Missing Wayland wire for wlr-export-dmabuf"sv;
BOOST_LOG(warning) << "[wlgrab] Missing Wayland wire for wlr-export-dmabuf"sv;
return {};
}

Expand All @@ -437,20 +451,20 @@ namespace platf {

display.roundtrip();

BOOST_LOG(info) << "-------- Start of Wayland monitor list --------"sv;
BOOST_LOG(info) << "[wlgrab] -------- Start of Wayland monitor list --------"sv;

for (int x = 0; x < interface.monitors.size(); ++x) {
auto monitor = interface.monitors[x].get();

wl::env_width = std::max(wl::env_width, (int) (monitor->viewport.offset_x + monitor->viewport.width));
wl::env_height = std::max(wl::env_height, (int) (monitor->viewport.offset_y + monitor->viewport.height));

BOOST_LOG(info) << "Monitor " << x << " is "sv << monitor->name << ": "sv << monitor->description;
BOOST_LOG(info) << "[wlgrab] Monitor " << x << " is "sv << monitor->name << ": "sv << monitor->description;

display_names.emplace_back(std::to_string(x));
}

BOOST_LOG(info) << "--------- End of Wayland monitor list ---------"sv;
BOOST_LOG(info) << "[wlgrab] --------- End of Wayland monitor list ---------"sv;

return display_names;
}
Expand Down