Commit 806f2559 authored by Lukáš Hrázký's avatar Lukáš Hrázký

Interface + implementation of getting device display info

Adds an interface method to the FrameCapture class to get the device
display info (device address and device display id) for each display of
the graphics device that is captured.

Also adds functions to the API implementing this functionality for X11
in variants with and without DRM (the non-DRM version is rather limited
and may not work for more complex setups) as well as some helper
functions to make it easier for plugins to implement this and avoid code
duplication.

Implements the new interface method for the two built-in plugins
(mjpeg-fallback and gst-plugin).
Signed-off-by: Lukáš Hrázký's avatarLukáš Hrázký <lhrazky@redhat.com>
Acked-by: default avatarJonathon Jongsma <jjongsma@redhat.com>
parent 8dc4dd6a
......@@ -34,8 +34,10 @@ SPICE_PROTOCOL_MIN_VER=0.12.14
PKG_CHECK_MODULES([SPICE_PROTOCOL], [spice-protocol >= $SPICE_PROTOCOL_MIN_VER])
AC_SUBST([SPICE_PROTOCOL_MIN_VER])
PKG_CHECK_MODULES(DRM, libdrm)
PKG_CHECK_MODULES(X11, x11)
PKG_CHECK_MODULES(XFIXES, xfixes)
PKG_CHECK_MODULES(XRANDR, xrandr)
PKG_CHECK_MODULES(JPEG, libjpeg, , [
AC_CHECK_LIB(jpeg, jpeg_destroy_decompress,
......
NULL =
public_includedir = $(includedir)/spice-streaming-agent
public_include_HEADERS = \
display-info.hpp \
error.hpp \
frame-capture.hpp \
plugin.hpp \
x11-display-info.hpp \
$(NULL)
/* \copyright
* Copyright 2018 Red Hat Inc. All rights reserved.
*/
#ifndef SPICE_STREAMING_AGENT_DISPLAY_INFO_HPP
#define SPICE_STREAMING_AGENT_DISPLAY_INFO_HPP
#include <vector>
#include <string>
namespace spice __attribute__ ((visibility ("default"))) {
namespace streaming_agent {
/**
* Lists graphics cards listed in the DRM sybsystem in /sys/class/drm.
* Throws an instance of Error in case of an I/O error.
*
* @return a vector of paths of all graphics cards present in /sys/class/drm
*/
std::vector<std::string> list_cards();
/**
* Reads a single number in hex format from a file.
* Throws an instance of Error in case of an I/O or parsing error.
*
* @param path the path to the file
* @return the number parsed from the file
*/
uint32_t read_hex_number_from_file(const std::string &path);
/**
* Resolves any symlinks and then extracts the PCI path from the canonical path
* to a card. Returns the path in the following format:
* "pci/<DOMAIN>/<SLOT>.<FUNCTION>/.../<SLOT>.<FUNCTION>"
*
* <DOMAIN> is the PCI domain, followed by <SLOT>.<FUNCTION> of any PCI bridges
* in the chain leading to the device. The last <SLOT>.<FUNCTION> is the
* graphics device. All of <DOMAIN>, <SLOT>, <FUNCTION> are hexadecimal numbers
* with the following number of digits:
* <DOMAIN>: 4
* <SLOT>: 2
* <FUNCTION>: 1
*
* @param device_path the path to the card
* @return the device address
*/
std::string get_device_address(const std::string &card_path);
}} // namespace spice::streaming_agent
#endif // SPICE_STREAMING_AGENT_DISPLAY_INFO_HPP
......@@ -6,7 +6,11 @@
*/
#ifndef SPICE_STREAMING_AGENT_FRAME_CAPTURE_HPP
#define SPICE_STREAMING_AGENT_FRAME_CAPTURE_HPP
#include <cstdint>
#include <cstdio>
#include <string>
#include <vector>
#include <spice/enums.h>
......@@ -29,6 +33,13 @@ struct FrameInfo
bool stream_start;
};
struct DeviceDisplayInfo
{
uint32_t stream_id;
std::string device_address;
uint32_t device_display_id;
};
/*!
* Pure base class implementing the frame capture
*/
......@@ -52,6 +63,8 @@ public:
* Get video codec used to encode last frame
*/
virtual SpiceVideoCodecType VideoCodecType() const = 0;
virtual std::vector<DeviceDisplayInfo> get_device_display_info() const = 0;
protected:
FrameCapture() = default;
FrameCapture(const FrameCapture&) = delete;
......
/* \copyright
* Copyright 2018 Red Hat Inc. All rights reserved.
*/
#ifndef SPICE_STREAMING_AGENT_X_DISPLAY_INFO_HPP
#define SPICE_STREAMING_AGENT_X_DISPLAY_INFO_HPP
#include <spice-streaming-agent/frame-capture.hpp>
#include <X11/Xlib.h>
namespace spice __attribute__ ((visibility ("default"))) {
namespace streaming_agent {
/**
* Looks up device display info by listing the xrandr outputs of @display and
* comparing them to card output names found under the DRM subsystem in
* /sys/class/drm.
*
* Throws an Error in case of error or when the DRM outputs cannot be found.
*
* @param display the X display to find the info for
* @return a vector of DeviceDisplayInfo structs containing the information of
* all the outputs used by the display
*/
std::vector<DeviceDisplayInfo> get_device_display_info_drm(Display *display);
/**
* Attempts to get the device display info without using DRM. It looks up the
* first card in /sys/class/drm that doesn't have its outputs listed (assuming
* that if the card in use has DRM outputs, it would be found using the
* get_display_info function) and simply uses the xrandr output index as the
* device_display_id. This can obviously incorrectly match the device address
* and the device_display_ids of two unrelated devices.
*
* Unfortunately, without DRM, there is no way to match xrandr objects with the
* real device.
*
* @param display the X display to find the info for
* @return a vector of DeviceDisplayInfo structs containing the information of
* all the outputs used by the display
*/
std::vector<DeviceDisplayInfo> get_device_display_info_no_drm(Display *display);
/**
* Lists xrandr outputs and returns their names in a vector of strings.
*
* @param display the X display
* @param window the X root window
* @return A vector of xrandr output names
*/
std::vector<std::string> get_xrandr_outputs(Display *display, Window window);
}} // namespace spice::streaming_agent
#endif // SPICE_STREAMING_AGENT_X_DISPLAY_INFO_HPP
......@@ -16,9 +16,11 @@ AM_CPPFLAGS = \
-DSPICE_STREAMING_AGENT_PROGRAM \
-I$(top_srcdir)/include \
-DPLUGINSDIR=\"$(pkglibdir)/plugins\" \
$(DRM_CFLAGS) \
$(SPICE_PROTOCOL_CFLAGS) \
$(X11_CFLAGS) \
$(XFIXES_CFLAGS) \
$(XRANDR_CFLAGS) \
$(NULL)
AM_CFLAGS = \
......@@ -50,8 +52,10 @@ spice_streaming_agent_LDADD = \
-ldl \
-lpthread \
libstreaming-utils.a \
$(DRM_LIBS) \
$(X11_LIBS) \
$(XFIXES_LIBS) \
$(XRANDR_LIBS) \
$(JPEG_LIBS) \
$(NULL)
......@@ -61,6 +65,7 @@ spice_streaming_agent_SOURCES = \
concrete-agent.hpp \
cursor-updater.cpp \
cursor-updater.hpp \
display-info.cpp \
frame-log.cpp \
frame-log.hpp \
mjpeg-fallback.cpp \
......@@ -69,7 +74,9 @@ spice_streaming_agent_SOURCES = \
jpeg.hpp \
stream-port.cpp \
stream-port.hpp \
utils.cpp \
utils.hpp \
x11-display-info.cpp \
$(NULL)
if HAVE_GST
......
/* \copyright
* Copyright 2018 Red Hat Inc. All rights reserved.
*/
#include <spice-streaming-agent/display-info.hpp>
#include "utils.hpp"
#include <spice-streaming-agent/error.hpp>
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <limits.h>
namespace spice {
namespace streaming_agent {
namespace {
std::string get_card_real_path(const std::string &card_path)
{
char real_path_buf[PATH_MAX];
if (realpath(card_path.c_str(), real_path_buf) == NULL) {
throw Error("Failed to realpath \"" + card_path + "\": " + strerror(errno));
}
return real_path_buf;
}
} // namespace
std::vector<std::string> list_cards()
{
std::string glob_path = "/sys/class/drm/card*";
auto globs = utils::glob(glob_path);
globs.erase(std::remove_if(globs.begin(), globs.end(), [](const std::string &glob) {
// if the path contains "-", it is an output, not a card, filter it out
return glob.find("-") != glob.npos;
}),
globs.end()
);
return globs;
}
uint32_t read_hex_number_from_file(const std::string &path)
{
uint32_t res;
std::ifstream vf(path);
if (vf.fail()) {
throw Error("Failed to open " + path + ": " + std::strerror(errno));
}
if (!(vf >> std::hex >> res)) {
throw Error("Failed to read from " + path + ": " + std::strerror(errno));
}
return res;
}
std::string get_device_address(const std::string &card_path)
{
std::string real_path = get_card_real_path(card_path);
// real_path is e.g. /sys/devices/pci0000:00/0000:00:02.0/drm/card0
std::string device_address = "pci/";
const std::string path_prefix = "/sys/devices/pci";
// if real_path doesn't start with path_prefix
if (real_path.compare(0, path_prefix.length(), path_prefix)) {
throw Error("Invalid device path \"" + real_path + "\"");
}
// /sys/devices/pci0000:00/0000:00:02.0/drm/card0
// ^ ptr
const char *ptr = real_path.c_str() + path_prefix.length();
// copy the domain
device_address += std::string(ptr, 4);
// /sys/devices/pci0000:00/0000:00:02.0/drm/card0
// ^ ptr
ptr += 7;
uint32_t domain, bus, slot, function, n;
while (sscanf(ptr, "/%x:%x:%x.%x%n", &domain, &bus, &slot, &function, &n) == 4) {
char pci_node[6];
snprintf(pci_node, 6, "/%02x.%01x", slot, function);
device_address += pci_node;
ptr += n;
}
return device_address;
}
}} // namespace spice::streaming_agent
......@@ -23,6 +23,8 @@
#include <spice-streaming-agent/plugin.hpp>
#include <spice-streaming-agent/frame-capture.hpp>
#include <spice-streaming-agent/x11-display-info.hpp>
#define gst_syslog(priority, str, ...) syslog(priority, "Gstreamer plugin: " str, ## __VA_ARGS__);
......@@ -76,6 +78,7 @@ public:
SpiceVideoCodecType VideoCodecType() const override {
return settings.codec;
}
std::vector<DeviceDisplayInfo> get_device_display_info() const override;
private:
void free_sample();
GstElement *get_encoder_plugin(const GstreamerEncoderSettings &settings, GstCapsUPtr &sink_caps);
......@@ -402,6 +405,17 @@ FrameInfo GstreamerFrameCapture::CaptureFrame()
return info;
}
std::vector<DeviceDisplayInfo> GstreamerFrameCapture::get_device_display_info() const
{
try {
return get_device_display_info_drm(dpy);
} catch (const std::exception &e) {
syslog(LOG_WARNING, "Failed to get device info using DRM: %s. Using no-DRM fallback.",
e.what());
return get_device_display_info_no_drm(dpy);
}
}
FrameCapture *GstreamerPlugin::CreateCapture()
{
return new GstreamerFrameCapture(settings);
......
......@@ -7,6 +7,9 @@
#include <config.h>
#include "mjpeg-fallback.hpp"
#include "jpeg.hpp"
#include <spice-streaming-agent/x11-display-info.hpp>
#include <cstring>
#include <exception>
#include <stdexcept>
......@@ -15,8 +18,6 @@
#include <syslog.h>
#include <X11/Xlib.h>
#include "jpeg.hpp"
using namespace spice::streaming_agent;
static inline uint64_t get_time()
......@@ -40,6 +41,7 @@ public:
SpiceVideoCodecType VideoCodecType() const override {
return SPICE_VIDEO_CODEC_TYPE_MJPEG;
}
std::vector<DeviceDisplayInfo> get_device_display_info() const override;
private:
MjpegSettings settings;
Display *dpy;
......@@ -137,6 +139,17 @@ FrameInfo MjpegFrameCapture::CaptureFrame()
return info;
}
std::vector<DeviceDisplayInfo> MjpegFrameCapture::get_device_display_info() const
{
try {
return get_device_display_info_drm(dpy);
} catch (const std::exception &e) {
syslog(LOG_WARNING, "Failed to get device info using DRM: %s. Using no-DRM fallback.",
e.what());
return get_device_display_info_no_drm(dpy);
}
}
FrameCapture *MjpegPlugin::CreateCapture()
{
return new MjpegFrameCapture(settings);
......
......@@ -217,6 +217,19 @@ do_capture(StreamPort &stream_port, FrameLog &frame_log)
throw std::runtime_error("cannot find a suitable capture system");
}
try {
std::vector<DeviceDisplayInfo> display_info = capture->get_device_display_info();
syslog(LOG_DEBUG, "Got device info of %lu devices from the plugin", display_info.size());
for (const auto &info : display_info) {
syslog(LOG_DEBUG, " id %u: device address %s, device display id: %u",
info.stream_id,
info.device_address.c_str(),
info.device_display_id);
}
} catch (const Error &e) {
syslog(LOG_ERR, "Error while getting device info: %s", e.what());
}
while (!quit_requested && streaming_requested) {
if (++frame_count % 100 == 0) {
syslog(LOG_DEBUG, "SENT %d frames", frame_count);
......
......@@ -5,6 +5,7 @@ AM_CPPFLAGS = \
-I$(top_srcdir)/include \
-I$(top_srcdir)/src \
-I$(top_srcdir)/src/unittests \
$(DRM_CFLAGS) \
$(SPICE_PROTOCOL_CFLAGS) \
$(NULL)
......@@ -43,13 +44,18 @@ hexdump_LDADD = \
test_mjpeg_fallback_SOURCES = \
test-mjpeg-fallback.cpp \
../display-info.cpp \
../jpeg.cpp \
../mjpeg-fallback.cpp \
../utils.cpp \
../x11-display-info.cpp \
$(NULL)
test_mjpeg_fallback_LDADD = \
$(DRM_LIBS) \
$(X11_LIBS) \
$(JPEG_LIBS) \
$(XRANDR_LIBS) \
$(NULL)
test_stream_port_SOURCES = \
......
/* \copyright
* Copyright 2018 Red Hat Inc. All rights reserved.
*/
#include "utils.hpp"
#include <spice-streaming-agent/error.hpp>
#include <glob.h>
#include <string.h>
#include <stdexcept>
namespace spice {
namespace streaming_agent {
namespace utils {
std::vector<std::string> glob(const std::string& pattern)
{
glob_t glob_result{};
std::vector<std::string> filenames;
int ret = glob(pattern.c_str(), GLOB_ERR, NULL, &glob_result);
if(ret != 0) {
globfree(&glob_result);
if (ret == GLOB_NOMATCH) {
return filenames;
}
throw Error("glob(" + pattern + ") failed with return value " + std::to_string(ret) +
": " + strerror(errno));
}
for(size_t i = 0; i < glob_result.gl_pathc; ++i) {
filenames.push_back(glob_result.gl_pathv[i]);
}
globfree(&glob_result);
return filenames;
}
}}} // namespace spice::streaming_agent::utils
......@@ -5,6 +5,8 @@
#ifndef SPICE_STREAMING_AGENT_UTILS_HPP
#define SPICE_STREAMING_AGENT_UTILS_HPP
#include <vector>
#include <string>
#include <syslog.h>
......@@ -12,6 +14,8 @@ namespace spice {
namespace streaming_agent {
namespace utils {
std::vector<std::string> glob(const std::string& pattern);
template<class T>
const T &syslog(const T &error) noexcept
{
......
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment