From 05dcff4f87f368d18f7fa17a64f8ba6f3fc75cac Mon Sep 17 00:00:00 2001 From: Loki Date: Mon, 23 Aug 2021 18:22:59 +0200 Subject: [PATCH] Ask Wayland what monitor outputs are available --- .gitmodules | 3 + CMakeLists.txt | 38 +++++++- cmake/FindWayland.cmake | 78 +++++++++++++++ sunshine/main.cpp | 5 + sunshine/platform/linux/wayland.cpp | 143 ++++++++++++++++++++++++++++ sunshine/platform/linux/wayland.h | 84 ++++++++++++++++ third-party/wayland-protocols | 1 + 7 files changed, 351 insertions(+), 1 deletion(-) create mode 100644 cmake/FindWayland.cmake create mode 100644 sunshine/platform/linux/wayland.cpp create mode 100644 sunshine/platform/linux/wayland.h create mode 160000 third-party/wayland-protocols diff --git a/.gitmodules b/.gitmodules index 153d2de8..4550c7d0 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,6 @@ [submodule "third-party/miniupnp"] path = third-party/miniupnp url = https://github.com/miniupnp/miniupnp +[submodule "third-party/wayland-protocols"] + path = third-party/wayland-protocols + url = https://github.com/wayland-project/wayland-protocols.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 45d4bf74..890a9fa5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -104,6 +104,7 @@ else() option(SUNSHINE_ENABLE_DRM "Enable KMS grab if available" ON) option(SUNSHINE_ENABLE_X11 "Enable X11 grab if available" ON) + option(SUNSHINE_ENABLE_WAYLAND "Enable building wayland specific code" ON) if(${SUNSHINE_ENABLE_X11}) find_package(X11) @@ -111,6 +112,9 @@ else() if(${SUNSHINE_ENABLE_DRM}) find_package(LIBDRM) endif() + if(${SUNSHINE_ENABLE_WAYLAND}) + find_package(Wayland) + endif() find_package(FFMPEG REQUIRED) @@ -126,8 +130,40 @@ else() list(APPEND PLATFORM_TARGET_FILES sunshine/platform/linux/kmsgrab.cpp) list(APPEND SUNSHINE_DEFINITIONS EGL_NO_X11=1) endif() + if(WAYLAND_FOUND) + macro(genWayland FILEPATH FILENAME) + message("wayland-scanner private-code ${CMAKE_SOURCE_DIR}/third-party/wayland-protocols/${FILEPATH}/${FILENAME}.xml ${CMAKE_BINARY_DIR}/generated-src/${FILENAME}.c") + message("wayland-scanner client-header ${CMAKE_SOURCE_DIR}/third-party/wayland-protocols/${FILEPATH}/${FILENAME}.xml ${CMAKE_BINARY_DIR}/generated-src/${FILENAME}.h") + execute_process( + COMMAND wayland-scanner private-code ${CMAKE_SOURCE_DIR}/third-party/wayland-protocols/${FILEPATH}/${FILENAME}.xml ${CMAKE_BINARY_DIR}/generated-src/${FILENAME}.c + COMMAND wayland-scanner client-header ${CMAKE_SOURCE_DIR}/third-party/wayland-protocols/${FILEPATH}/${FILENAME}.xml ${CMAKE_BINARY_DIR}/generated-src/${FILENAME}.h + + RESULT_VARIABLE EXIT_INT + ) + + if(NOT ${EXIT_INT} EQUAL 0) + message(FATAL_ERROR "wayland-scanner failed") + endif() + + list(APPEND PLATFORM_TARGET_FILES + ${CMAKE_BINARY_DIR}/generated-src/${FILENAME}.c + ${CMAKE_BINARY_DIR}/generated-src/${FILENAME}.h + ) + endmacro() + + genWayland(unstable/xdg-output xdg-output-unstable-v1) + + include_directories( + ${WAYLAND_INCLUDE_DIRS} + ${CMAKE_BINARY_DIR}/generated-src + ) + + list(APPEND PLATFORM_LIBRARIES ${WAYLAND_LIBRARIES}) + list(APPEND PLATFORM_TARGET_FILES sunshine/platform/linux/wayland.h) + list(APPEND PLATFORM_TARGET_FILES sunshine/platform/linux/wayland.cpp) + endif() if(NOT X11_FOUND AND NOT LIBDRM_FOUND) - message(FATAL "Couldn't find either x11 or libdrm") + message(FATAL_ERROR "Couldn't find either x11 or libdrm") endif() list(APPEND PLATFORM_TARGET_FILES diff --git a/cmake/FindWayland.cmake b/cmake/FindWayland.cmake new file mode 100644 index 00000000..377f0545 --- /dev/null +++ b/cmake/FindWayland.cmake @@ -0,0 +1,78 @@ +# Try to find Wayland on a Unix system +# +# This will define: +# +# WAYLAND_FOUND - True if Wayland is found +# WAYLAND_LIBRARIES - Link these to use Wayland +# WAYLAND_INCLUDE_DIRS - Include directory for Wayland +# WAYLAND_DEFINITIONS - Compiler flags for using Wayland +# +# In addition the following more fine grained variables will be defined: +# +# Wayland_Client_FOUND WAYLAND_CLIENT_INCLUDE_DIRS WAYLAND_CLIENT_LIBRARIES +# Wayland_Server_FOUND WAYLAND_SERVER_INCLUDE_DIRS WAYLAND_SERVER_LIBRARIES +# Wayland_EGL_FOUND WAYLAND_EGL_INCLUDE_DIRS WAYLAND_EGL_LIBRARIES +# Wayland_Cursor_FOUND WAYLAND_CURSOR_INCLUDE_DIRS WAYLAND_CURSOR_LIBRARIES +# +# Copyright (c) 2013 Martin Gräßlin +# 2020 Georges Basile Stavracas Neto +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +IF (NOT WIN32) + + # Use pkg-config to get the directories and then use these values + # in the find_path() and find_library() calls + find_package(PkgConfig) + PKG_CHECK_MODULES(PKG_WAYLAND QUIET wayland-client wayland-server wayland-egl wayland-cursor) + + set(WAYLAND_DEFINITIONS ${PKG_WAYLAND_CFLAGS}) + + find_path(WAYLAND_CLIENT_INCLUDE_DIRS NAMES wayland-client.h HINTS ${PKG_WAYLAND_INCLUDE_DIRS}) + find_library(WAYLAND_CLIENT_LIBRARIES NAMES wayland-client HINTS ${PKG_WAYLAND_LIBRARY_DIRS}) + if(WAYLAND_CLIENT_INCLUDE_DIRS AND WAYLAND_CLIENT_LIBRARIES) + set(Wayland_Client_FOUND TRUE) + else() + set(Wayland_Client_FOUND FALSE) + endif() + mark_as_advanced(WAYLAND_CLIENT_INCLUDE_DIRS WAYLAND_CLIENT_LIBRARIES) + + find_path(WAYLAND_CURSOR_INCLUDE_DIRS NAMES wayland-cursor.h HINTS ${PKG_WAYLAND_INCLUDE_DIRS}) + find_library(WAYLAND_CURSOR_LIBRARIES NAMES wayland-cursor HINTS ${PKG_WAYLAND_LIBRARY_DIRS}) + if(WAYLAND_CURSOR_INCLUDE_DIRS AND WAYLAND_CURSOR_LIBRARIES) + set(Wayland_Cursor_FOUND TRUE) + else() + set(Wayland_Cursor_FOUND FALSE) + endif() + mark_as_advanced(WAYLAND_CURSOR_INCLUDE_DIRS WAYLAND_CURSOR_LIBRARIES) + + find_path(WAYLAND_EGL_INCLUDE_DIRS NAMES wayland-egl.h HINTS ${PKG_WAYLAND_INCLUDE_DIRS}) + find_library(WAYLAND_EGL_LIBRARIES NAMES wayland-egl HINTS ${PKG_WAYLAND_LIBRARY_DIRS}) + if(WAYLAND_EGL_INCLUDE_DIRS AND WAYLAND_EGL_LIBRARIES) + set(Wayland_EGL_FOUND TRUE) + else() + set(Wayland_EGL_FOUND FALSE) + endif() + mark_as_advanced(WAYLAND_EGL_INCLUDE_DIRS WAYLAND_EGL_LIBRARIES) + + find_path(WAYLAND_SERVER_INCLUDE_DIRS NAMES wayland-server.h HINTS ${PKG_WAYLAND_INCLUDE_DIRS}) + find_library(WAYLAND_SERVER_LIBRARIES NAMES wayland-server HINTS ${PKG_WAYLAND_LIBRARY_DIRS}) + if(WAYLAND_SERVER_INCLUDE_DIRS AND WAYLAND_SERVER_LIBRARIES) + set(Wayland_Server_FOUND TRUE) + else() + set(Wayland_Server_FOUND FALSE) + endif() + mark_as_advanced(WAYLAND_SERVER_INCLUDE_DIRS WAYLAND_SERVER_LIBRARIES) + + set(WAYLAND_INCLUDE_DIRS ${WAYLAND_CLIENT_INCLUDE_DIRS} ${WAYLAND_SERVER_INCLUDE_DIRS} ${WAYLAND_EGL_INCLUDE_DIRS} ${WAYLAND_CURSOR_INCLUDE_DIRS}) + set(WAYLAND_LIBRARIES ${WAYLAND_CLIENT_LIBRARIES} ${WAYLAND_SERVER_LIBRARIES} ${WAYLAND_EGL_LIBRARIES} ${WAYLAND_CURSOR_LIBRARIES}) + mark_as_advanced(WAYLAND_INCLUDE_DIRS WAYLAND_LIBRARIES) + + list(REMOVE_DUPLICATES WAYLAND_INCLUDE_DIRS) + + include(FindPackageHandleStandardArgs) + + find_package_handle_standard_args(Wayland REQUIRED_VARS WAYLAND_LIBRARIES WAYLAND_INCLUDE_DIRS HANDLE_COMPONENTS) + +ENDIF () diff --git a/sunshine/main.cpp b/sunshine/main.cpp index b2bed72f..c95e2752 100644 --- a/sunshine/main.cpp +++ b/sunshine/main.cpp @@ -26,6 +26,8 @@ #include "upnp.h" #include "video.h" +#include "platform/linux/wayland.h" + #include "platform/common.h" extern "C" { #include @@ -239,6 +241,9 @@ int main(int argc, char *argv[]) { shutdown_event->raise(true); }); + wl::test(); + return 0; + proc::refresh(config::stream.file_apps); auto deinit_guard = platf::init(); diff --git a/sunshine/platform/linux/wayland.cpp b/sunshine/platform/linux/wayland.cpp new file mode 100644 index 00000000..f546c7d9 --- /dev/null +++ b/sunshine/platform/linux/wayland.cpp @@ -0,0 +1,143 @@ +#include +#include + +#include + +#include "sunshine/main.h" +#include "sunshine/platform/common.h" +#include "sunshine/utility.h" +#include "wayland.h" + +extern const wl_interface wl_output_interface; + +using namespace std::literals; + +// Disable warning for converting incompatible functions +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpedantic" + +namespace wl { +void test() { + display_t display; + + if(display.init()) { + return; + } + + interface_t interface { display.registry() }; + + display.roundtrip(); + + for(auto &monitor : interface.monitors) { + monitor->listen(interface.output_manager); + } + + display.roundtrip(); +} + +int display_t::init(const char *display_name) { + if(!display_name) { + display_name = std::getenv("WAYLAND_DISPLAY"); + } + + if(!display_name) { + BOOST_LOG(error) << "Environment variable WAYLAND_DISPLAY has not been defined"sv; + 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; + return -1; + } + + BOOST_LOG(info) << "Found display ["sv << display_name << ']'; + + return 0; +} + +void display_t::roundtrip() { + wl_display_roundtrip(display_internal.get()); +} + +wl_registry *display_t::registry() { + return wl_display_get_registry(display_internal.get()); +} + +inline monitor_t::monitor_t(wl_output *output) : output { output } {} + +inline void monitor_t::xdg_name(zxdg_output_v1 *, const char *name) { + this->name = name; + + BOOST_LOG(info) << "Name: "sv << this->name; +} + +inline void monitor_t::xdg_description(zxdg_output_v1 *, const char *description) { + this->description = description; + + BOOST_LOG(info) << "Found monitor: "sv << this->description; +} + +inline 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; +} + +inline void monitor_t::xdg_size(zxdg_output_v1 *, std::int32_t width, std::int32_t height) { + viewport.width = width; + viewport.height = height; + + BOOST_LOG(info) << "Resolution: "sv << width << 'x' << height; +} + +inline void monitor_t::xdg_done(zxdg_output_v1 *) { + BOOST_LOG(info) << "All info about monitor ["sv << name << "] has been send"sv; +} + +inline void monitor_t::listen(zxdg_output_manager_v1 *output_manager) { + auto xdg_output = zxdg_output_manager_v1_get_xdg_output(output_manager, output); + +#define CLASS_CALL(x, y) x = (decltype(x))&y + + CLASS_CALL(listener.name, monitor_t::xdg_name); + CLASS_CALL(listener.logical_size, monitor_t::xdg_size); + CLASS_CALL(listener.logical_position, monitor_t::xdg_position); + CLASS_CALL(listener.done, monitor_t::xdg_done); + CLASS_CALL(listener.description, monitor_t::xdg_description); + +#undef CLASS_CALL + zxdg_output_v1_add_listener(xdg_output, &listener, this); +} + +inline interface_t::interface_t(wl_registry *registry) + : output_manager { nullptr }, listener { + (decltype(wl_registry_listener::global))&interface_t::add_interface, + (decltype(wl_registry_listener::global_remove))&interface_t::del_interface, + } { + wl_registry_add_listener(registry, &listener, this); +} + +inline void interface_t::add_interface(wl_registry *registry, std::uint32_t id, const char *interface, std::uint32_t version) { + BOOST_LOG(debug) << "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; + monitors.emplace_back( + std::make_unique( + (wl_output *)wl_registry_bind(registry, id, &wl_output_interface, version))); + } + else if(!std::strcmp(interface, zxdg_output_manager_v1_interface.name)) { + BOOST_LOG(info) << "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); + } +} + +inline void interface_t::del_interface(wl_registry *registry, uint32_t id) { + BOOST_LOG(info) << "Delete: "sv << id; +} + +} // namespace wl + +#pragma GCC diagnostic pop \ No newline at end of file diff --git a/sunshine/platform/linux/wayland.h b/sunshine/platform/linux/wayland.h new file mode 100644 index 00000000..a0cfa86d --- /dev/null +++ b/sunshine/platform/linux/wayland.h @@ -0,0 +1,84 @@ +#ifndef SUNSHINE_WAYLAND_H +#define SUNSHINE_WAYLAND_H + +#include + +namespace wl { +using display_internal_t = util::safe_ptr; + +class monitor_t { +public: + monitor_t(monitor_t &&) = delete; + monitor_t(const monitor_t &) = delete; + + monitor_t &operator=(const monitor_t &) = delete; + monitor_t &operator=(monitor_t &&) = delete; + + monitor_t(wl_output *output); + + void xdg_name(zxdg_output_v1 *, const char *name); + void xdg_description(zxdg_output_v1 *, const char *description); + void xdg_position(zxdg_output_v1 *, std::int32_t x, std::int32_t y); + void xdg_size(zxdg_output_v1 *, std::int32_t width, std::int32_t height); + void xdg_done(zxdg_output_v1 *); + + void listen(zxdg_output_manager_v1 *output_manager); + + wl_output *output; + + std::string name; + std::string description; + + platf::touch_port_t viewport; + + zxdg_output_v1_listener listener; +}; + +class interface_t { + struct bind_t { + std::uint32_t id; + std::uint32_t version; + }; + +public: + interface_t(interface_t &&) = delete; + interface_t(const interface_t &) = delete; + + interface_t &operator=(const interface_t &) = delete; + interface_t &operator=(interface_t &&) = delete; + + interface_t(wl_registry *registry); + + std::vector> monitors; + zxdg_output_manager_v1 *output_manager; + +private: + void add_interface(wl_registry *registry, std::uint32_t id, const char *interface, std::uint32_t version); + void del_interface(wl_registry *registry, uint32_t id); + + wl_registry_listener listener; +}; + +class display_t { +public: + /** + * Initialize display with display_name + * If display_name == nullptr -> display_name = std::getenv("WAYLAND_DISPLAY") + */ + int init(const char *display_name = nullptr); + + // Roundtrip with Wayland connection + void roundtrip(); + + // Get the registry associated with the display + // No need to manually free the registry + wl_registry *registry(); + +private: + display_internal_t display_internal; +}; + +void test(); +} // namespace wl + +#endif \ No newline at end of file diff --git a/third-party/wayland-protocols b/third-party/wayland-protocols new file mode 160000 index 00000000..7dffa6f3 --- /dev/null +++ b/third-party/wayland-protocols @@ -0,0 +1 @@ +Subproject commit 7dffa6f346ae32f7888177d74a45459997059767