mirror of
https://github.com/LizardByte/Sunshine.git
synced 2024-11-18 02:09:49 +00:00
409 lines
11 KiB
C++
409 lines
11 KiB
C++
//
|
|
// Created by loki on 6/21/19.
|
|
//
|
|
|
|
#include "sunshine/platform/common.h"
|
|
|
|
#include <fstream>
|
|
|
|
#include <X11/X.h>
|
|
#include <X11/Xlib.h>
|
|
#include <X11/Xutil.h>
|
|
#include <X11/extensions/Xfixes.h>
|
|
#include <X11/extensions/Xrandr.h>
|
|
#include <sys/ipc.h>
|
|
#include <sys/shm.h>
|
|
#include <xcb/shm.h>
|
|
#include <xcb/xfixes.h>
|
|
|
|
#include "sunshine/config.h"
|
|
#include "sunshine/main.h"
|
|
#include "sunshine/task_pool.h"
|
|
|
|
#include "vaapi.h"
|
|
|
|
using namespace std::literals;
|
|
|
|
namespace platf {
|
|
|
|
void freeImage(XImage *);
|
|
void freeX(XFixesCursorImage *);
|
|
|
|
using xcb_connect_t = util::safe_ptr<xcb_connection_t, xcb_disconnect>;
|
|
using xcb_img_t = util::c_ptr<xcb_shm_get_image_reply_t>;
|
|
|
|
using xdisplay_t = util::safe_ptr_v2<Display, int, XCloseDisplay>;
|
|
using ximg_t = util::safe_ptr<XImage, freeImage>;
|
|
using xcursor_t = util::safe_ptr<XFixesCursorImage, freeX>;
|
|
|
|
using crtc_info_t = util::safe_ptr<_XRRCrtcInfo, XRRFreeCrtcInfo>;
|
|
using output_info_t = util::safe_ptr<_XRROutputInfo, XRRFreeOutputInfo>;
|
|
using screen_res_t = util::safe_ptr<_XRRScreenResources, XRRFreeScreenResources>;
|
|
|
|
class shm_id_t {
|
|
public:
|
|
shm_id_t() : id { -1 } {}
|
|
shm_id_t(int id) : id { id } {}
|
|
shm_id_t(shm_id_t &&other) noexcept : id(other.id) {
|
|
other.id = -1;
|
|
}
|
|
|
|
~shm_id_t() {
|
|
if(id != -1) {
|
|
shmctl(id, IPC_RMID, nullptr);
|
|
id = -1;
|
|
}
|
|
}
|
|
int id;
|
|
};
|
|
|
|
class shm_data_t {
|
|
public:
|
|
shm_data_t() : data { (void *)-1 } {}
|
|
shm_data_t(void *data) : data { data } {}
|
|
|
|
shm_data_t(shm_data_t &&other) noexcept : data(other.data) {
|
|
other.data = (void *)-1;
|
|
}
|
|
|
|
~shm_data_t() {
|
|
if((std::uintptr_t)data != -1) {
|
|
shmdt(data);
|
|
}
|
|
}
|
|
|
|
void *data;
|
|
};
|
|
|
|
struct x11_img_t : public img_t {
|
|
ximg_t img;
|
|
};
|
|
|
|
struct shm_img_t : public img_t {
|
|
~shm_img_t() override {
|
|
delete[] data;
|
|
data = nullptr;
|
|
}
|
|
};
|
|
|
|
void blend_cursor(Display *display, img_t &img, int offsetX, int offsetY) {
|
|
xcursor_t overlay { XFixesGetCursorImage(display) };
|
|
|
|
if(!overlay) {
|
|
BOOST_LOG(error) << "Couldn't get cursor from XFixesGetCursorImage"sv;
|
|
return;
|
|
}
|
|
|
|
overlay->x -= overlay->xhot;
|
|
overlay->y -= overlay->yhot;
|
|
|
|
overlay->x -= offsetX;
|
|
overlay->y -= offsetY;
|
|
|
|
overlay->x = std::max((short)0, overlay->x);
|
|
overlay->y = std::max((short)0, overlay->y);
|
|
|
|
auto pixels = (int *)img.data;
|
|
|
|
auto screen_height = img.height;
|
|
auto screen_width = img.width;
|
|
|
|
auto delta_height = std::min<uint16_t>(overlay->height, std::max(0, screen_height - overlay->y));
|
|
auto delta_width = std::min<uint16_t>(overlay->width, std::max(0, screen_width - overlay->x));
|
|
for(auto y = 0; y < delta_height; ++y) {
|
|
auto overlay_begin = &overlay->pixels[y * overlay->width];
|
|
auto overlay_end = &overlay->pixels[y * overlay->width + delta_width];
|
|
|
|
auto pixels_begin = &pixels[(y + overlay->y) * (img.row_pitch / img.pixel_pitch) + overlay->x];
|
|
|
|
std::for_each(overlay_begin, overlay_end, [&](long pixel) {
|
|
int *pixel_p = (int *)&pixel;
|
|
|
|
auto colors_in = (uint8_t *)pixels_begin;
|
|
|
|
auto alpha = (*(uint *)pixel_p) >> 24u;
|
|
if(alpha == 255) {
|
|
*pixels_begin = *pixel_p;
|
|
}
|
|
else {
|
|
auto colors_out = (uint8_t *)pixel_p;
|
|
colors_in[0] = colors_out[0] + (colors_in[0] * (255 - alpha) + 255 / 2) / 255;
|
|
colors_in[1] = colors_out[1] + (colors_in[1] * (255 - alpha) + 255 / 2) / 255;
|
|
colors_in[2] = colors_out[2] + (colors_in[2] * (255 - alpha) + 255 / 2) / 255;
|
|
}
|
|
++pixels_begin;
|
|
});
|
|
}
|
|
}
|
|
|
|
struct x11_attr_t : public display_t {
|
|
xdisplay_t xdisplay;
|
|
Window xwindow;
|
|
XWindowAttributes xattr;
|
|
|
|
mem_type_e mem_type;
|
|
|
|
/*
|
|
* Last X (NOT the streamed monitor!) size.
|
|
* This way we can trigger reinitialization if the dimensions changed while streaming
|
|
*/
|
|
int lastWidth, lastHeight;
|
|
|
|
x11_attr_t(mem_type_e mem_type) : xdisplay { XOpenDisplay(nullptr) }, xwindow {}, xattr {}, mem_type { mem_type } {
|
|
XInitThreads();
|
|
}
|
|
|
|
int init() {
|
|
if(!xdisplay) {
|
|
BOOST_LOG(error) << "Could not open X11 display"sv;
|
|
return -1;
|
|
}
|
|
|
|
xwindow = DefaultRootWindow(xdisplay.get());
|
|
|
|
refresh();
|
|
|
|
int streamedMonitor = -1;
|
|
if(!config::video.output_name.empty()) {
|
|
streamedMonitor = (int)util::from_view(config::video.output_name);
|
|
}
|
|
|
|
if(streamedMonitor != -1) {
|
|
BOOST_LOG(info) << "Configuring selected monitor ("sv << streamedMonitor << ") to stream"sv;
|
|
screen_res_t screenr { XRRGetScreenResources(xdisplay.get(), xwindow) };
|
|
int output = screenr->noutput;
|
|
|
|
output_info_t result;
|
|
int monitor = 0;
|
|
for(int x = 0; x < output; ++x) {
|
|
output_info_t out_info { XRRGetOutputInfo(xdisplay.get(), screenr.get(), screenr->outputs[x]) };
|
|
if(out_info && out_info->connection == RR_Connected) {
|
|
if(monitor++ == streamedMonitor) {
|
|
result = std::move(out_info);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!result) {
|
|
BOOST_LOG(error) << "Could not stream display number ["sv << streamedMonitor << "], there are only ["sv << monitor << "] displays."sv;
|
|
return -1;
|
|
}
|
|
|
|
crtc_info_t crt_info { XRRGetCrtcInfo(xdisplay.get(), screenr.get(), result->crtc) };
|
|
BOOST_LOG(info)
|
|
<< "Streaming display: "sv << result->name << " with res "sv << crt_info->width << 'x' << crt_info->height << " offset by "sv << crt_info->x << 'x' << crt_info->y;
|
|
|
|
width = crt_info->width;
|
|
height = crt_info->height;
|
|
offset_x = crt_info->x;
|
|
offset_y = crt_info->y;
|
|
}
|
|
else {
|
|
width = xattr.width;
|
|
height = xattr.height;
|
|
}
|
|
|
|
lastWidth = xattr.width;
|
|
lastHeight = xattr.height;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Called when the display attributes should change.
|
|
*/
|
|
void refresh() {
|
|
XGetWindowAttributes(xdisplay.get(), xwindow, &xattr); //Update xattr's
|
|
}
|
|
|
|
capture_e snapshot(img_t *img_out_base, std::chrono::milliseconds timeout, bool cursor) override {
|
|
refresh();
|
|
|
|
//The whole X server changed, so we gotta reinit everything
|
|
if(xattr.width != lastWidth || xattr.height != lastHeight) {
|
|
BOOST_LOG(warning) << "X dimensions changed in non-SHM mode, request reinit"sv;
|
|
return capture_e::reinit;
|
|
}
|
|
XImage *img { XGetImage(xdisplay.get(), xwindow, offset_x, offset_y, width, height, AllPlanes, ZPixmap) };
|
|
|
|
auto img_out = (x11_img_t *)img_out_base;
|
|
img_out->width = img->width;
|
|
img_out->height = img->height;
|
|
img_out->data = (uint8_t *)img->data;
|
|
img_out->row_pitch = img->bytes_per_line;
|
|
img_out->pixel_pitch = img->bits_per_pixel / 8;
|
|
img_out->img.reset(img);
|
|
|
|
if(cursor) {
|
|
blend_cursor(xdisplay.get(), *img_out_base, offset_x, offset_y);
|
|
}
|
|
|
|
return capture_e::ok;
|
|
}
|
|
|
|
std::shared_ptr<img_t> alloc_img() override {
|
|
return std::make_shared<x11_img_t>();
|
|
}
|
|
|
|
std::shared_ptr<hwdevice_t> make_hwdevice(pix_fmt_e pix_fmt) override {
|
|
if(mem_type == mem_type_e::vaapi) {
|
|
return egl::make_hwdevice(width, height);
|
|
}
|
|
|
|
return std::make_shared<hwdevice_t>();
|
|
}
|
|
|
|
int dummy_img(img_t *img) override {
|
|
snapshot(img, 0s, true);
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
struct shm_attr_t : public x11_attr_t {
|
|
xdisplay_t shm_xdisplay; // Prevent race condition with x11_attr_t::xdisplay
|
|
xcb_connect_t xcb;
|
|
xcb_screen_t *display;
|
|
std::uint32_t seg;
|
|
|
|
shm_id_t shm_id;
|
|
|
|
shm_data_t data;
|
|
|
|
util::TaskPool::task_id_t refresh_task_id;
|
|
|
|
void delayed_refresh() {
|
|
refresh();
|
|
|
|
refresh_task_id = task_pool.pushDelayed(&shm_attr_t::delayed_refresh, 2s, this).task_id;
|
|
}
|
|
|
|
shm_attr_t(mem_type_e mem_type) : x11_attr_t(mem_type), shm_xdisplay { XOpenDisplay(nullptr) } {
|
|
refresh_task_id = task_pool.pushDelayed(&shm_attr_t::delayed_refresh, 2s, this).task_id;
|
|
}
|
|
|
|
~shm_attr_t() override {
|
|
while(!task_pool.cancel(refresh_task_id))
|
|
;
|
|
}
|
|
|
|
capture_e snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor) override {
|
|
//The whole X server changed, so we gotta reinit everything
|
|
if(xattr.width != lastWidth || xattr.height != lastHeight) {
|
|
BOOST_LOG(warning) << "X dimensions changed in SHM mode, request reinit"sv;
|
|
return capture_e::reinit;
|
|
}
|
|
else {
|
|
auto img_cookie = xcb_shm_get_image_unchecked(xcb.get(), display->root, offset_x, offset_y, width, height, ~0, XCB_IMAGE_FORMAT_Z_PIXMAP, seg, 0);
|
|
|
|
xcb_img_t img_reply { xcb_shm_get_image_reply(xcb.get(), img_cookie, nullptr) };
|
|
if(!img_reply) {
|
|
BOOST_LOG(error) << "Could not get image reply"sv;
|
|
return capture_e::reinit;
|
|
}
|
|
|
|
std::copy_n((std::uint8_t *)data.data, frame_size(), img->data);
|
|
|
|
if(cursor) {
|
|
blend_cursor(shm_xdisplay.get(), *img, offset_x, offset_y);
|
|
}
|
|
|
|
return capture_e::ok;
|
|
}
|
|
}
|
|
|
|
std::shared_ptr<img_t> alloc_img() override {
|
|
auto img = std::make_shared<shm_img_t>();
|
|
img->width = width;
|
|
img->height = height;
|
|
img->pixel_pitch = 4;
|
|
img->row_pitch = img->pixel_pitch * width;
|
|
img->data = new std::uint8_t[height * img->row_pitch];
|
|
|
|
return img;
|
|
}
|
|
|
|
int dummy_img(platf::img_t *img) override {
|
|
return 0;
|
|
}
|
|
|
|
int init() {
|
|
if(x11_attr_t::init()) {
|
|
return 1;
|
|
}
|
|
|
|
shm_xdisplay.reset(XOpenDisplay(nullptr));
|
|
xcb.reset(xcb_connect(nullptr, nullptr));
|
|
if(xcb_connection_has_error(xcb.get())) {
|
|
return -1;
|
|
}
|
|
|
|
if(!xcb_get_extension_data(xcb.get(), &xcb_shm_id)->present) {
|
|
BOOST_LOG(error) << "Missing SHM extension"sv;
|
|
|
|
return -1;
|
|
}
|
|
|
|
auto iter = xcb_setup_roots_iterator(xcb_get_setup(xcb.get()));
|
|
display = iter.data;
|
|
seg = xcb_generate_id(xcb.get());
|
|
|
|
shm_id.id = shmget(IPC_PRIVATE, frame_size(), IPC_CREAT | 0777);
|
|
if(shm_id.id == -1) {
|
|
BOOST_LOG(error) << "shmget failed"sv;
|
|
return -1;
|
|
}
|
|
|
|
xcb_shm_attach(xcb.get(), seg, shm_id.id, false);
|
|
data.data = shmat(shm_id.id, nullptr, 0);
|
|
|
|
if((uintptr_t)data.data == -1) {
|
|
BOOST_LOG(error) << "shmat failed"sv;
|
|
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
std::uint32_t frame_size() {
|
|
return width * height * 4;
|
|
}
|
|
};
|
|
|
|
std::shared_ptr<display_t> display(platf::mem_type_e hwdevice_type) {
|
|
if(hwdevice_type != platf::mem_type_e::system && hwdevice_type != platf::mem_type_e::vaapi) {
|
|
BOOST_LOG(error) << "Could not initialize display with the given hw device type."sv;
|
|
return nullptr;
|
|
}
|
|
|
|
// Attempt to use shared memory X11 to avoid copying the frame
|
|
auto shm_disp = std::make_shared<shm_attr_t>(hwdevice_type);
|
|
|
|
auto status = shm_disp->init();
|
|
if(status > 0) {
|
|
// x11_attr_t::init() failed, don't bother trying again.
|
|
return nullptr;
|
|
}
|
|
|
|
if(status == 0) {
|
|
return shm_disp;
|
|
}
|
|
|
|
// Fallback
|
|
auto x11_disp = std::make_shared<x11_attr_t>(hwdevice_type);
|
|
if(x11_disp->init()) {
|
|
return nullptr;
|
|
}
|
|
|
|
return x11_disp;
|
|
}
|
|
|
|
void freeImage(XImage *p) {
|
|
XDestroyImage(p);
|
|
}
|
|
void freeX(XFixesCursorImage *p) {
|
|
XFree(p);
|
|
}
|
|
} // namespace platf
|