// // Created by loki on 6/21/19. // #include "sunshine/platform/common.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "sunshine/task_pool.h" #include "sunshine/config.h" #include "sunshine/main.h" namespace platf { using namespace std::literals; void freeImage(XImage*); void freeX(XFixesCursorImage*); using ifaddr_t = util::safe_ptr; using xcb_connect_t = util::safe_ptr; using xcb_img_t = util::c_ptr; using xcb_cursor_img = util::c_ptr; using xdisplay_t = util::safe_ptr_v2; using ximg_t = util::safe_ptr; using xcursor_t = util::safe_ptr; 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); data = (void*) -1; } } 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(overlay->height,std::max(0, screen_height - overlay->y)); auto delta_width = std::min(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; Display* displayDisplay; /* * Remember last X (NOT the streamed monitor!) size. This way we can trigger reinitialization if the dimensions changed while streaming */ int lastWidth,lastHeight; /* * Offsets for when streaming a specific monitor. By default, they are 0. */ int displayOffsetX,displayOffsetY; x11_attr_t() : xdisplay { displayDisplay = XOpenDisplay(nullptr) }, xwindow { }, xattr { } { XInitThreads(); if (!xdisplay) { BOOST_LOG(fatal) << "Could not open x11 display"sv; log_flush(); std::abort(); } xwindow = DefaultRootWindow(xdisplay.get()); refresh(); int streamedMonitor = -1; if(!config::video.output_name.empty()) { streamedMonitor = (int)util::from_view(config::video.output_name); } //If the value has been set at all if (streamedMonitor != -1) { BOOST_LOG(info) << "Configuring selected monitor ("<< streamedMonitor<<") to stream. If it fails here, you may need Xrandr >= 1.5"sv; XRRScreenResources *screenr = XRRGetScreenResources(displayDisplay, xwindow); // This is the key right here. Use XRRScreenResources::noutput int output = screenr->noutput; if (streamedMonitor >= output) { BOOST_LOG(error)<< "Could not stream selected display number because there aren't so many."sv; } else { XRROutputInfo* out_info = XRRGetOutputInfo(displayDisplay, screenr, screenr->outputs[streamedMonitor]); if (NULL == out_info || out_info->connection != RR_Connected) { BOOST_LOG(error)<< "Could not stream selected display because it doesn't seem to be connected"sv; } else { XRRCrtcInfo* crt_info = XRRGetCrtcInfo(displayDisplay, screenr, out_info->crtc); BOOST_LOG(info)<<"Streaming display: "<name<<" with res "<width<<" x "<height<<+" offset by "<x<<" x "<y<<"."sv; width = crt_info -> width; height = crt_info -> height; displayOffsetX = crt_info -> x; displayOffsetY = crt_info -> y; XRRFreeCrtcInfo(crt_info); } XRRFreeOutputInfo(out_info); } XRRFreeScreenResources(screenr); } else { width = xattr.width; height = xattr.height; } lastWidth = xattr.width; lastHeight = xattr.height; } /** * 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, displayOffsetX, displayOffsetY, 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,displayOffsetX,displayOffsetY); } return capture_e::ok; } std::shared_ptr alloc_img() override { return std::make_shared(); } 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() : x11_attr_t(), 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,displayOffsetX, displayOffsetY, 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,displayOffsetX,displayOffsetY); } return capture_e::ok; } } std::shared_ptr alloc_img() override { auto img = std::make_shared(); 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() { 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; } /* * Commented out resetting of the sizes when intializing X in SHM mode. It might be wrong. Expect issues. This is the default mode, so poisoning those variables is not desired */ // width = display->width_in_pixels; // height = display->height_in_pixels; return 0; } std::uint32_t frame_size() { return width * height * 4; } }; struct mic_attr_t: public mic_t { pa_sample_spec ss; util::safe_ptr mic; explicit mic_attr_t(pa_sample_format format, std::uint32_t sample_rate, std::uint8_t channels) : ss { format, sample_rate, channels }, mic { } { } capture_e sample(std::vector &sample_buf) override { auto sample_size = sample_buf.size(); auto buf = sample_buf.data(); int status; if (pa_simple_read(mic.get(), buf, sample_size * 2, &status)) { BOOST_LOG(error) << "pa_simple_read() failed: "sv << pa_strerror(status); return capture_e::error; } return capture_e::ok; } }; std::shared_ptr shm_display() { auto shm = std::make_shared(); if (shm->init()) { return nullptr; } return shm; } std::shared_ptr display(platf::dev_type_e hwdevice_type) { if (hwdevice_type != platf::dev_type_e::none) { BOOST_LOG(error)<< "Could not initialize display with the given hw device type."sv; return nullptr; } auto shm_disp = shm_display(); //Attempt to use shared memory X11 to avoid copying the frame if (!shm_disp) { return std::make_shared(); //Fallback to standard way if else } return shm_disp; } std::unique_ptr microphone(std::uint32_t sample_rate) { auto mic = std::make_unique(PA_SAMPLE_S16LE, sample_rate, 2); int status; const char *audio_sink = "@DEFAULT_MONITOR@"; if (!config::audio.sink.empty()) { audio_sink = config::audio.sink.c_str(); } mic->mic.reset( pa_simple_new(nullptr, "sunshine", pa_stream_direction_t::PA_STREAM_RECORD, audio_sink, "sunshine-record", &mic->ss, nullptr, nullptr, &status)); if (!mic->mic) { auto err_str = pa_strerror(status); BOOST_LOG(error) << "pa_simple_new() failed: "sv << err_str; log_flush(); std::abort(); } return mic; } ifaddr_t get_ifaddrs() { ifaddrs *p { nullptr }; getifaddrs(&p); return ifaddr_t { p }; } std::string from_sockaddr(const sockaddr *const ip_addr) { char data[INET6_ADDRSTRLEN]; auto family = ip_addr->sa_family; if (family == AF_INET6) { inet_ntop(AF_INET6, &((sockaddr_in6*) ip_addr)->sin6_addr, data, INET6_ADDRSTRLEN); } if (family == AF_INET) { inet_ntop(AF_INET, &((sockaddr_in*) ip_addr)->sin_addr, data, INET_ADDRSTRLEN); } return std::string { data }; } std::pair from_sockaddr_ex(const sockaddr *const ip_addr) { char data[INET6_ADDRSTRLEN]; auto family = ip_addr->sa_family; std::uint16_t port; if (family == AF_INET6) { inet_ntop(AF_INET6, &((sockaddr_in6*) ip_addr)->sin6_addr, data, INET6_ADDRSTRLEN); port = ((sockaddr_in6*) ip_addr)->sin6_port; } if (family == AF_INET) { inet_ntop(AF_INET, &((sockaddr_in*) ip_addr)->sin_addr, data, INET_ADDRSTRLEN); port = ((sockaddr_in*) ip_addr)->sin_port; } return { port, std::string {data}}; } std::string get_mac_address(const std::string_view &address) { auto ifaddrs = get_ifaddrs(); for (auto pos = ifaddrs.get(); pos != nullptr; pos = pos->ifa_next) { if (pos->ifa_addr && address == from_sockaddr(pos->ifa_addr)) { std::ifstream mac_file("/sys/class/net/"s + pos->ifa_name + "/address"); if (mac_file.good()) { std::string mac_address; std::getline(mac_file, mac_address); return mac_address; } } } BOOST_LOG(warning) << "Unable to find MAC address for "sv << address; return "00:00:00:00:00:00"s; } void freeImage(XImage *p) { XDestroyImage(p); } void freeX(XFixesCursorImage *p) { XFree(p); } }