diff --git a/.gitmodules b/.gitmodules index db296a31..93af2e68 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,6 +7,3 @@ [submodule "ViGEmClient"] path = ViGEmClient url = https://github.com/ViGEm/ViGEmClient -[submodule "pre-compiled"] - path = pre-compiled - url = https://github.com/TheElixZammuto/sunshine-prebuilt.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 24c316a7..1347b7d7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 2.8) +cmake_minimum_required(VERSION 3.0) project(Sunshine) @@ -7,56 +7,65 @@ set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}) # On MSYS2, building a stand-alone binary that links with ffmpeg is not possible, # Therefore, ffmpeg, libx264 and libx265 must be build from source if(WIN32) - option(SUNSHINE_STANDALONE "Compile stand-alone binary of Sunshine" OFF) - if(SUNSHINE_STANDALONE) - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static") + file( + DOWNLOAD "https://github.com/TheElixZammuto/sunshine-prebuilt/releases/download/1.0.0/pre-compiled.zip" "${CMAKE_CURRENT_BINARY_DIR}/pre-compiled.zip" + TIMEOUT 60 + EXPECTED_HASH SHA256=5d59986bd7f619eaaf82b2dd56b5127b747c9cbe8db61e3b898ff6b485298ed6) + + file(ARCHIVE_EXTRACT + INPUT "${CMAKE_CURRENT_BINARY_DIR}/pre-compiled.zip" + DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/pre-compiled) + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static") - if(NOT DEFINED SUNSHINE_PREPARED_BINARIES) - set(SUNSHINE_PREPARED_BINARIES "${CMAKE_CURRENT_SOURCE_DIR}/pre-compiled/windows") - endif() - list(PREPEND PLATFORM_LIBRARIES - C:/msys64/mingw64/lib/gcc/x86_64-w64-mingw32/${CMAKE_CXX_COMPILER_VERSION}/libstdc++.a - C:/msys64/mingw64/x86_64-w64-mingw32/lib/libwinpthread.a - ) - - set(FFMPEG_INCLUDE_DIRS - ${SUNSHINE_PREPARED_BINARIES}/include) - set(FFMPEG_LIBRARIES - ${SUNSHINE_PREPARED_BINARIES}/lib/libavcodec.a - ${SUNSHINE_PREPARED_BINARIES}/lib/libavdevice.a - ${SUNSHINE_PREPARED_BINARIES}/lib/libavfilter.a - ${SUNSHINE_PREPARED_BINARIES}/lib/libavformat.a - ${SUNSHINE_PREPARED_BINARIES}/lib/libavutil.a - ${SUNSHINE_PREPARED_BINARIES}/lib/libpostproc.a - ${SUNSHINE_PREPARED_BINARIES}/lib/libswresample.a - ${SUNSHINE_PREPARED_BINARIES}/lib/libswscale.a - ${SUNSHINE_PREPARED_BINARIES}/lib/libx264.a - ${SUNSHINE_PREPARED_BINARIES}/lib/libx265.a - ${SUNSHINE_PREPARED_BINARIES}/lib/libhdr10plus.a - z lzma bcrypt C:/msys64/mingw64/lib/libiconv.a) + if(NOT DEFINED SUNSHINE_PREPARED_BINARIES) + set(SUNSHINE_PREPARED_BINARIES "${CMAKE_CURRENT_BINARY_DIR}/pre-compiled/windows") endif() -else() - set(SUNSHINE_STANDALONE OFF) -endif() + set(FFMPEG_INCLUDE_DIRS + ${SUNSHINE_PREPARED_BINARIES}/include) + set(FFMPEG_LIBRARIES + ${SUNSHINE_PREPARED_BINARIES}/lib/libavcodec.a + ${SUNSHINE_PREPARED_BINARIES}/lib/libavdevice.a + ${SUNSHINE_PREPARED_BINARIES}/lib/libavfilter.a + ${SUNSHINE_PREPARED_BINARIES}/lib/libavformat.a + ${SUNSHINE_PREPARED_BINARIES}/lib/libavutil.a + ${SUNSHINE_PREPARED_BINARIES}/lib/libpostproc.a + ${SUNSHINE_PREPARED_BINARIES}/lib/libswresample.a + ${SUNSHINE_PREPARED_BINARIES}/lib/libswscale.a + ${SUNSHINE_PREPARED_BINARIES}/lib/libx264.a + ${SUNSHINE_PREPARED_BINARIES}/lib/libx265.a + ${SUNSHINE_PREPARED_BINARIES}/lib/libhdr10plus.a + z lzma bcrypt libiconv.a) +endif() add_subdirectory(Simple-Web-Server) add_subdirectory(moonlight-common-c/enet) find_package(Threads REQUIRED) find_package(OpenSSL REQUIRED) -if(NOT SUNSHINE_STANDALONE) - find_package(FFmpeg REQUIRED) -endif() - list(APPEND SUNSHINE_COMPILE_OPTIONS -fPIC -Wall -Wno-missing-braces -Wno-maybe-uninitialized -Wno-sign-compare) if(WIN32) + file( + DOWNLOAD "https://github.com/TheElixZammuto/sunshine-prebuilt/releases/download/1.0.0/pre-compiled.zip" "${CMAKE_CURRENT_BINARY_DIR}/pre-compiled.zip" + TIMEOUT 60 + EXPECTED_HASH SHA256=5d59986bd7f619eaaf82b2dd56b5127b747c9cbe8db61e3b898ff6b485298ed6) + + file(ARCHIVE_EXTRACT + INPUT "${CMAKE_CURRENT_BINARY_DIR}/pre-compiled.zip" + DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/pre-compiled) + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static") + + if(NOT DEFINED SUNSHINE_PREPARED_BINARIES) + set(SUNSHINE_PREPARED_BINARIES "${CMAKE_CURRENT_BINARY_DIR}/pre-compiled/windows") + endif() + add_subdirectory(tools) #This is temporary, only tools for Windows are needed, for now list(APPEND SUNSHINE_DEFINITIONS APPS_JSON="apps_windows.json") - include_directories( - ViGEmClient/include) + + include_directories(ViGEmClient/include) + set(PLATFORM_TARGET_FILES sunshine/platform/windows/input.cpp sunshine/platform/windows/display.h @@ -69,7 +78,31 @@ if(WIN32) ViGEmClient/include/ViGEm/Common.h ViGEmClient/include/ViGEm/Util.h ViGEmClient/include/ViGEm/km/BusShared.h) + + set(OPENSSL_LIBRARIES + libssl.a + libcrypto.a) + + set(FFMPEG_INCLUDE_DIRS + ${SUNSHINE_PREPARED_BINARIES}/include) + set(FFMPEG_LIBRARIES + ${SUNSHINE_PREPARED_BINARIES}/lib/libavcodec.a + ${SUNSHINE_PREPARED_BINARIES}/lib/libavdevice.a + ${SUNSHINE_PREPARED_BINARIES}/lib/libavfilter.a + ${SUNSHINE_PREPARED_BINARIES}/lib/libavformat.a + ${SUNSHINE_PREPARED_BINARIES}/lib/libavutil.a + ${SUNSHINE_PREPARED_BINARIES}/lib/libpostproc.a + ${SUNSHINE_PREPARED_BINARIES}/lib/libswresample.a + ${SUNSHINE_PREPARED_BINARIES}/lib/libswscale.a + ${SUNSHINE_PREPARED_BINARIES}/lib/libx264.a + ${SUNSHINE_PREPARED_BINARIES}/lib/libx265.a + ${SUNSHINE_PREPARED_BINARIES}/lib/libhdr10plus.a + z lzma bcrypt libiconv.a) + list(PREPEND PLATFORM_LIBRARIES + libstdc++.a + libwinpthread.a + libssp.a winmm ksuser wsock32 @@ -85,6 +118,7 @@ else() list(APPEND SUNSHINE_DEFINITIONS APPS_JSON="apps_linux.json") find_package(X11 REQUIRED) + find_package(FFmpeg REQUIRED) set(PLATFORM_TARGET_FILES sunshine/platform/linux/display.cpp sunshine/platform/linux/input.cpp) @@ -95,6 +129,7 @@ else() xcb xcb-shm xcb-xfixes + Xrandr ${X11_LIBRARIES} evdev pulse @@ -179,12 +214,6 @@ if(NOT SUNSHINE_ASSETS_DIR) set(SUNSHINE_ASSETS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/assets") endif() -if(SUNSHINE_STANDALONE) - set(OPENSSL_LIBRARIES - C:/msys64/mingw64/lib/libssl.a - C:/msys64/mingw64/lib/libcrypto.a) -endif() - list(APPEND SUNSHINE_EXTERNAL_LIBRARIES ${CMAKE_THREAD_LIBS_INIT} stdc++fs diff --git a/README.md b/README.md index fc414370..3a1bac44 100644 --- a/README.md +++ b/README.md @@ -50,10 +50,10 @@ sunshine needs access to uinput to create mouse and gamepad events: ### Requirements: - MSYS2 : mingw-w64-x86_64-openssl mingw-w64-x86_64-cmake mingw-w64-x86_64-toolchain mingw-w64-x86_64-ffmpeg mingw-w64-x86_64-boost + mingw-w64-x86_64-openssl mingw-w64-x86_64-cmake mingw-w64-x86_64-toolchain mingw-w64-x86_64-opus mingw-w64-x86_64-x265 mingw-w64-x86_64-boost git yasm nasm diffutils make ### Compilation: -- `git clone https://github.com/loki-47-6F-64/sunshine.git --recurse-submodules` +- `git clone https://github.com/loki-47-6F-64/sunshine.git --recursive` - `cd sunshine && mkdir build && cd build` - `cmake -G"Unix Makefiles" ..` - `make` @@ -61,17 +61,6 @@ sunshine needs access to uinput to create mouse and gamepad events: ### Setup: - **OPTIONAL** Gamepad support: Download and run 'ViGEmBus_Setup_1.16.116.exe' from [https://github.com/ViGEm/ViGEmBus/releases] -### Static build -#### Requirements: - MSYS2 : mingw-w64-x86_64-openssl mingw-w64-x86_64-cmake mingw-w64-x86_64-toolchain mingw-w64-x86_64-ffmpeg mingw-w64-x86_64-boost git-lfs - -#### Compilation: -- `git lfs install` -- `git clone https://github.com/loki-47-6F-64/sunshine.git --recurse-submodules` -- `cd sunshine && mkdir build && cd build` -- `cmake -DSUNSHINE_STANDALONE=ON -G"Unix Makefiles" ..` -- `make` - # Common diff --git a/appveyor.yml b/appveyor.yml index 2bbc9b2c..e0115dab 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,5 +1,5 @@ image: - - Ubuntu + - Ubuntu2004 - Visual Studio 2019 environment: @@ -8,8 +8,8 @@ environment: - BUILD_TYPE: Release install: - - sh: sudo apt update - - sh: sudo apt install -y build-essential cmake libssl-dev libavdevice-dev libboost-thread-dev libboost-filesystem-dev libboost-log-dev libpulse-dev libopus-dev libxtst-dev libx11-dev libxfixes-dev libevdev-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev + - sh: sudo apt update --ignore-missing + - sh: sudo apt install -y build-essential cmake libssl-dev libavdevice-dev libboost-thread-dev libboost-filesystem-dev libboost-log-dev libpulse-dev libopus-dev libxtst-dev libx11-dev libxrandr-dev libxfixes-dev libevdev-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev - cmd: C:\msys64\usr\bin\bash -lc "pacman --needed --noconfirm -S mingw-w64-x86_64-openssl mingw-w64-x86_64-cmake mingw-w64-x86_64-toolchain mingw-w64-x86_64-opus mingw-w64-x86_64-x265 mingw-w64-x86_64-boost git yasm nasm diffutils make" before_build: @@ -21,7 +21,7 @@ build_script: - cmd: set OLDPATH=%PATH% - cmd: set PATH=C:\msys64\mingw64\bin - sh: cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DSUNSHINE_EXECUTABLE_PATH=sunshine -DSUNSHINE_ASSETS_DIR=/etc/sunshine .. - - cmd: cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DSUNSHINE_STANDALONE=ON -DSUNSHINE_ASSETS_DIR=assets -G "MinGW Makefiles" .. + - cmd: cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DSUNSHINE_ASSETS_DIR=assets -G "MinGW Makefiles" .. - sh: make -j$(nproc) - cmd: mingw32-make -j2 - cmd: set PATH=%OLDPATH% diff --git a/assets/sunshine.conf b/assets/sunshine.conf index f887e45a..356acd79 100644 --- a/assets/sunshine.conf +++ b/assets/sunshine.conf @@ -93,6 +93,9 @@ # adapter_name = Radeon RX 580 Series # output_name = \\.\DISPLAY1 +# !! Linux only !! +# Set the display number to stream. I have no idea how they are numbered. They start from 1, usually. +# output_name = 1 ############################################### # FFmpeg software encoding parameters @@ -121,12 +124,12 @@ # If set to 1, Sunshine will not advertise support for HEVC # If set to 2, Sunshine will advertise support for HEVC Main profile # If set to 3, Sunshine will advertise support for HEVC Main and Main10 (HDR) profiles -# hevc_mode = 0 +# hevc_mode = 2 # Force a specific encoder, otherwise Sunshine will use the first encoder that is available # supported encoders: # nvenc -# amdvce +# amdvce # NOTE: alpha stage. The cursor is not yet displayed # software # # encoder = nvenc @@ -171,6 +174,31 @@ ########################## # nv_coder = auto +##################################### AMD ##################################### +###### presets ########### +# default +# speed +# balanced +########################## +# amd_preset = balanced +# +####### rate control ##### +# auto -- let ffmpeg decide rate control +# constqp -- constant QP mode +# vbr -- variable bitrate +# cbr -- constant bitrate +# cbr_hq -- cbr high quality +# cbr_ld_hq -- cbr low delay high quality +# vbr_hq -- vbr high quality +########################## +# amd_rc = auto + +###### h264 entropy ###### +# auto -- let ffmpeg nvenc decide the entropy encoding +# cabac +# cavlc +########################## +# amd_coder = auto ############################################## # Some configurable parameters, are merely toggles for specific features diff --git a/pre-compiled b/pre-compiled deleted file mode 160000 index 36f68727..00000000 --- a/pre-compiled +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 36f687271239e25b2b7085f764194a61e2b9c605 diff --git a/sunshine/config.cpp b/sunshine/config.cpp index 8561d298..88cc4502 100644 --- a/sunshine/config.cpp +++ b/sunshine/config.cpp @@ -165,7 +165,7 @@ video_t video { {}, // encoder {}, // adapter_name - {} // output_name + {}, // output_name }; audio_t audio {}; @@ -392,6 +392,9 @@ int apply_flags(const char *line) { case '1': config::sunshine.flags[config::flag::FRESH_STATE].flip(); break; + case 'p': + config::sunshine.flags[config::flag::CONST_PIN].flip(); + break; default: std::cout << "Warning: Unrecognized flag: ["sv << *line << ']' << std::endl; ret = -1; diff --git a/sunshine/config.h b/sunshine/config.h index f3beb5bd..f67ac12b 100644 --- a/sunshine/config.h +++ b/sunshine/config.h @@ -77,7 +77,8 @@ namespace flag { enum flag_e : std::size_t { PIN_STDIN = 0, // Read PIN from stdin instead of http FRESH_STATE, // Do not load or save state - FLAG_SIZE + FLAG_SIZE, + CONST_PIN= 4 // Use "universal" pin }; } diff --git a/sunshine/nvhttp.cpp b/sunshine/nvhttp.cpp index 6ce58a7e..da402706 100644 --- a/sunshine/nvhttp.cpp +++ b/sunshine/nvhttp.cpp @@ -360,7 +360,11 @@ void pair(std::shared_ptr> &add_cert, std::shared_ ptr->second.async_insert_pin.salt = std::move(args.at("salt"s)); - if(config::sunshine.flags[config::flag::PIN_STDIN]) { + if(config::sunshine.flags[config::flag::CONST_PIN]) { + std::string pin("6174"); + getservercert(ptr->second, tree, pin); + } + else if(config::sunshine.flags[config::flag::PIN_STDIN]) { std::string pin; std::cout << "Please insert pin: "sv; diff --git a/sunshine/platform/linux/display.cpp b/sunshine/platform/linux/display.cpp index 1452920e..cde96cf7 100644 --- a/sunshine/platform/linux/display.cpp +++ b/sunshine/platform/linux/display.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -26,11 +27,12 @@ #include "sunshine/config.h" #include "sunshine/main.h" -namespace platf { +namespace platf +{ using namespace std::literals; -void freeImage(XImage *); -void freeX(XFixesCursorImage *); +void freeImage(XImage*); +void freeX(XFixesCursorImage*); using ifaddr_t = util::safe_ptr; using xcb_connect_t = util::safe_ptr; @@ -44,13 +46,13 @@ 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(int id) : id { id } {} shm_id_t(shm_id_t &&other) noexcept : id(other.id) { other.id = -1; } ~shm_id_t() { - if(id != -1) { + if (id != -1) { shmctl(id, IPC_RMID, nullptr); id = -1; } @@ -60,83 +62,114 @@ public: 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() : data { (void*) -1 } + { + } + shm_data_t(void *data) : data { data } + { } - ~shm_data_t() { - if((std::uintptr_t)data != -1) { + 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; + data = (void*) -1; } } void *data; }; -struct x11_img_t : public img_t { +struct x11_img_t: public img_t { ximg_t img; }; -struct shm_img_t : public img_t { - ~shm_img_t() override { +struct shm_img_t: public img_t { + ~shm_img_t() override + { delete[] data; data = nullptr; } }; -void blend_cursor(Display *display, img_t &img) { +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; + if (!overlay) { + BOOST_LOG(error) + << "Couldn't get cursor from XFixesGetCursorImage"sv; return; } overlay->x -= overlay->xhot; overlay->y -= overlay->yhot; - overlay->x = std::max((short)0, overlay->x); - overlay->y = std::max((short)0, overlay->y); + overlay->x -= offsetX; + overlay->y -= offsetY; - auto pixels = (int*)img.data; + 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 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 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 pixels_begin = &pixels[(y + overlay->y)* (img.row_pitch / img.pixel_pitch) + overlay->x]; - auto colors_in = (uint8_t*)pixels_begin; + std::for_each(overlay_begin, overlay_end,[&](long pixel) { + int *pixel_p = (int*) &pixel; - auto alpha = (*(uint*)pixel_p) >> 24u; - if(alpha == 255) { + 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; + 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 { - x11_attr_t() : xdisplay {XOpenDisplay(nullptr) }, xwindow { }, xattr {} { - if(!xdisplay) { +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(); @@ -146,39 +179,81 @@ struct x11_attr_t : public display_t { refresh(); - width = xattr.width; - height = xattr.height; + 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; } - void refresh() { - XGetWindowAttributes(xdisplay.get(), xwindow, &xattr); + /** + * 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(); - if(width != xattr.width || height != xattr.height) { + //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) }; - XImage *img { XGetImage( - xdisplay.get(), - xwindow, - 0, 0, - xattr.width, xattr.height, - AllPlanes, ZPixmap) - }; - - auto img_out = (x11_img_t*)img_out_base; + 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->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); + if (cursor) { + blend_cursor(xdisplay.get(), *img_out_base,displayOffsetX,displayOffsetY); } return capture_e::ok; @@ -192,13 +267,9 @@ struct x11_attr_t : public display_t { snapshot(img, 0s, true); return 0; } - - xdisplay_t xdisplay; - Window xwindow; - XWindowAttributes xattr; }; -struct shm_attr_t : public x11_attr_t { +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; @@ -209,54 +280,50 @@ struct shm_attr_t : public x11_attr_t { 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; + 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() : 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)); + while (!task_pool.cancel(refresh_task_id)); } capture_e snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor) override { - if(width != xattr.width || height != xattr.height) { + //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); - auto img_cookie = xcb_shm_get_image_unchecked( - xcb.get(), - display->root, - 0, 0, - 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; + } - 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::copy_n((std::uint8_t*)data.data, frame_size(), img->data); - - if(cursor) { - blend_cursor(shm_xdisplay.get(), *img); - } - - return capture_e::ok; } std::shared_ptr alloc_img() override { auto img = std::make_shared(); - img->width = width; + img->width = width; img->height = height; img->pixel_pitch = 4; img->row_pitch = img->pixel_pitch * width; @@ -272,12 +339,15 @@ struct shm_attr_t : public x11_attr_t { int init() { shm_xdisplay.reset(XOpenDisplay(nullptr)); xcb.reset(xcb_connect(nullptr, nullptr)); - if(xcb_connection_has_error(xcb.get())) { + 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; + if (!xcb_get_extension_data(xcb.get(), &xcb_shm_id)->present) + { + BOOST_LOG(error) + << "Missing SHM extension"sv; return -1; } @@ -287,22 +357,29 @@ struct shm_attr_t : public x11_attr_t { 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; + 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; + if ((uintptr_t) data.data == -1) + { + BOOST_LOG(error) + << "shmat failed"sv; return -1; } - width = display->width_in_pixels; - height = display->height_in_pixels; + /* + * 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; } @@ -312,18 +389,22 @@ struct shm_attr_t : public x11_attr_t { } }; -struct mic_attr_t : public mic_t { +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 {} {} + 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); + 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; } @@ -335,7 +416,7 @@ struct mic_attr_t : public mic_t { std::shared_ptr shm_display() { auto shm = std::make_shared(); - if(shm->init()) { + if (shm->init()) { return nullptr; } @@ -343,14 +424,15 @@ std::shared_ptr shm_display() { } std::shared_ptr display(platf::dev_type_e hwdevice_type) { - if(hwdevice_type != platf::dev_type_e::none) { + 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(); + auto shm_disp = shm_display(); //Attempt to use shared memory X11 to avoid copying the frame - if(!shm_disp) { - return std::make_shared(); + if (!shm_disp) { + return std::make_shared(); //Fallback to standard way if else } return shm_disp; @@ -362,15 +444,16 @@ std::unique_ptr microphone(std::uint32_t sample_rate) { int status; const char *audio_sink = "@DEFAULT_MONITOR@"; - if(!config::audio.sink.empty()) { + 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) - ); + pa_simple_new(nullptr, "sunshine", + pa_stream_direction_t::PA_STREAM_RECORD, audio_sink, + "sunshine-record", &mic->ss, nullptr, nullptr, &status)); - if(!mic->mic) { + if (!mic->mic) { auto err_str = pa_strerror(status); BOOST_LOG(error) << "pa_simple_new() failed: "sv << err_str; @@ -393,12 +476,14 @@ 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_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); + if (family == AF_INET) { + inet_ntop(AF_INET, &((sockaddr_in*) ip_addr)->sin_addr, data, + INET_ADDRSTRLEN); } return std::string { data }; @@ -409,32 +494,36 @@ std::pair from_sockaddr_ex(const sockaddr *const ip_ 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_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; + 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 } }; + 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)) { + 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()) { + 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; + BOOST_LOG(warning) + << "Unable to find MAC address for "sv << address; return "00:00:00:00:00:00"s; } diff --git a/sunshine/platform/windows/display_base.cpp b/sunshine/platform/windows/display_base.cpp index c510b044..29b53523 100644 --- a/sunshine/platform/windows/display_base.cpp +++ b/sunshine/platform/windows/display_base.cpp @@ -90,6 +90,7 @@ int display_base_t::init() { FreeLibrary(user32); }); */ + dxgi::factory1_t::pointer factory_p {}; dxgi::adapter_t::pointer adapter_p {}; dxgi::output_t::pointer output_p {}; @@ -150,8 +151,6 @@ int display_base_t::init() { } D3D_FEATURE_LEVEL featureLevels[] { - D3D_FEATURE_LEVEL_12_1, - D3D_FEATURE_LEVEL_12_0, D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_1, @@ -164,7 +163,6 @@ int display_base_t::init() { status = adapter->QueryInterface(IID_IDXGIAdapter, (void**)&adapter_p); if(FAILED(status)) { BOOST_LOG(error) << "Failed to query IDXGIAdapter interface"sv; - return -1; } diff --git a/sunshine/platform/windows/display_vram.cpp b/sunshine/platform/windows/display_vram.cpp index 55fce9a4..0d87b40c 100644 --- a/sunshine/platform/windows/display_vram.cpp +++ b/sunshine/platform/windows/display_vram.cpp @@ -99,6 +99,7 @@ util::buffer_t make_cursor_image(util::buffer_t &&im class hwdevice_t : public platf::hwdevice_t { public: + hwdevice_t(std::vector *hwdevices_p) : hwdevices_p { hwdevices_p } {} hwdevice_t() = delete; @@ -168,8 +169,8 @@ public: auto &processor_in = it->second; D3D11_VIDEO_PROCESSOR_STREAM stream[] { - { TRUE, 0, 0, 0, 0, nullptr, processor_in.get(), nullptr }, - { TRUE, 0, 0, 0, 0, nullptr, cursor_in.get(), nullptr } + { TRUE, 0, 0, 0, 0, nullptr, processor_in.get() }, + { TRUE, 0, 0, 0, 0, nullptr, cursor_in.get() } }; auto status = ctx->VideoProcessorBlt(processor.get(), processor_out.get(), 0, cursor_visible ? 2 : 1, stream); @@ -233,6 +234,12 @@ public: } processor_e.reset(vp_e_p); + D3D11_VIDEO_PROCESSOR_CAPS proc_caps; + processor_e->GetVideoProcessorCaps(&proc_caps); + if(!(proc_caps.FeatureCaps & D3D11_VIDEO_PROCESSOR_FEATURE_CAPS_ALPHA_STREAM)) { + BOOST_LOG(warning) << "VideoProcessorSetStreamAlpha() not supported, hardware accelerated mouse cannot be added to the video stream"sv; + } + video::processor_t::pointer processor_p; status = device->CreateVideoProcessor(processor_e.get(), 0, &processor_p); if(FAILED(status)) { @@ -241,6 +248,9 @@ public: } processor.reset(processor_p); + // Tell video processor alpha values need to be enabled + ctx->VideoProcessorSetStreamAlpha(processor.get(), 1, TRUE, 1.0f); + D3D11_TEXTURE2D_DESC t {}; t.Width = out_width; t.Height = out_height; @@ -275,9 +285,6 @@ public: } processor_out.reset(processor_out_p); - // Tell video processor alpha values need to be enabled - ctx->VideoProcessorSetStreamAlpha(processor.get(), 1, TRUE, 1.0f); - device_p->AddRef(); data = device_p; return 0; @@ -373,7 +380,7 @@ capture_e display_vram_t::snapshot(platf::img_t *img_base, std::chrono::millisec dxgi::texture2d_t::pointer tex_p {}; auto status = device->CreateTexture2D(&t, &data, &tex_p); if(FAILED(status)) { - BOOST_LOG(error) << "Failed to create dummy texture [0x"sv << util::hex(status).to_string_view() << ']'; + BOOST_LOG(error) << "Failed to create mouse texture [0x"sv << util::hex(status).to_string_view() << ']'; return capture_e::error; } texture2d_t texture { tex_p }; diff --git a/sunshine/platform/windows/input.cpp b/sunshine/platform/windows/input.cpp index 60120738..284f3c83 100755 --- a/sunshine/platform/windows/input.cpp +++ b/sunshine/platform/windows/input.cpp @@ -17,6 +17,9 @@ using namespace std::literals; using adapteraddrs_t = util::c_ptr; +volatile HDESK _lastKnownInputDesktop = NULL; +HDESK pairInputDesktop(); + class vigem_t { public: using client_t = util::safe_ptr<_VIGEM_CLIENT_T, vigem_free>; @@ -171,15 +174,21 @@ void move_mouse(input_t &input, int deltaX, int deltaY) { mi.dwFlags = MOUSEEVENTF_MOVE; mi.dx = deltaX; mi.dy = deltaY; - + +retry: auto send = SendInput(1, &i, sizeof(INPUT)); if(send != 1) { + auto hDesk = pairInputDesktop(); + if (_lastKnownInputDesktop != hDesk) { + _lastKnownInputDesktop = hDesk; + goto retry; + } BOOST_LOG(warning) << "Couldn't send mouse movement input"sv; } } void button_mouse(input_t &input, int button, bool release) { - constexpr SHORT KEY_STATE_DOWN = 0x8000; + constexpr auto KEY_STATE_DOWN = (SHORT)0x8000; INPUT i {}; @@ -218,8 +227,14 @@ void button_mouse(input_t &input, int button, bool release) { return; } +retry: auto send = SendInput(1, &i, sizeof(INPUT)); if(send != 1) { + auto hDesk = pairInputDesktop(); + if (_lastKnownInputDesktop != hDesk) { + _lastKnownInputDesktop = hDesk; + goto retry; + } BOOST_LOG(warning) << "Couldn't send mouse button input"sv; } } @@ -233,9 +248,15 @@ void scroll(input_t &input, int distance) { mi.dwFlags = MOUSEEVENTF_WHEEL; mi.mouseData = distance; +retry: auto send = SendInput(1, &i, sizeof(INPUT)); if(send != 1) { - BOOST_LOG(warning) << "Couldn't send moue movement input"sv; + auto hDesk = pairInputDesktop(); + if (_lastKnownInputDesktop != hDesk) { + _lastKnownInputDesktop = hDesk; + goto retry; + } + BOOST_LOG(warning) << "Couldn't send mouse scroll input"sv; } } @@ -282,9 +303,15 @@ void keyboard(input_t &input, uint16_t modcode, bool release) { ki.dwFlags |= KEYEVENTF_KEYUP; } +retry: auto send = SendInput(1, &i, sizeof(INPUT)); if(send != 1) { - BOOST_LOG(warning) << "Couldn't send moue movement input"sv; + auto hDesk = pairInputDesktop(); + if (_lastKnownInputDesktop != hDesk) { + _lastKnownInputDesktop = hDesk; + goto retry; + } + BOOST_LOG(warning) << "Couldn't send keyboard input"sv; } } @@ -327,6 +354,24 @@ int thread_priority() { return SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST) ? 0 : 1; } +HDESK pairInputDesktop() { + auto hDesk = OpenInputDesktop(DF_ALLOWOTHERACCOUNTHOOK, FALSE, GENERIC_ALL); + if (NULL == hDesk) { + auto err = GetLastError(); + BOOST_LOG(error) << "Failed to OpenInputDesktop [0x"sv << util::hex(err).to_string_view() << ']'; + } + else { + BOOST_LOG(info) << std::endl << "Opened desktop [0x"sv << util::hex(hDesk).to_string_view() << ']'; + if (!SetThreadDesktop(hDesk) ) { + auto err = GetLastError(); + BOOST_LOG(error) << "Failed to SetThreadDesktop [0x"sv << util::hex(err).to_string_view() << ']'; + } + CloseDesktop(hDesk); + } + + return hDesk; +} + void freeInput(void *p) { auto vigem = (vigem_t*)p; diff --git a/sunshine/video.cpp b/sunshine/video.cpp index 90e1ded5..b2e23855 100644 --- a/sunshine/video.cpp +++ b/sunshine/video.cpp @@ -373,9 +373,11 @@ static encoder_t software { static std::vector encoders { #ifdef _WIN32 nvenc, +#endif + software, +#ifdef _WIN32 amdvce, #endif - software }; void reset_display(std::shared_ptr &disp, AVHWDeviceType type) { @@ -467,7 +469,14 @@ void captureThread( std::this_thread::sleep_for(100ms); } - reset_display(disp, encoder.dev_type); + while(capture_ctx_queue->running()) { + reset_display(disp, encoder.dev_type); + + if(disp) { + break; + } + std::this_thread::sleep_for(200ms); + } if(!disp) { return; } @@ -844,7 +853,16 @@ encode_e encode_run_sync(std::vector> &synce const auto &encoder = encoders.front(); std::shared_ptr disp; - reset_display(disp, encoder.dev_type); + + while(encode_session_ctx_queue.running()) { + reset_display(disp, encoder.dev_type); + if(disp) { + break; + } + + std::this_thread::sleep_for(200ms); + } + if(!disp) { return encode_e::error; }