Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/common/include/display_device/detail/json_serializer.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ namespace display_device {
DD_JSON_DECLARE_SERIALIZE_TYPE(Resolution)
DD_JSON_DECLARE_SERIALIZE_TYPE(Rational)
DD_JSON_DECLARE_SERIALIZE_TYPE(Point)
DD_JSON_DECLARE_SERIALIZE_TYPE(EdidData)
DD_JSON_DECLARE_SERIALIZE_TYPE(EnumeratedDevice::Info)
DD_JSON_DECLARE_SERIALIZE_TYPE(EnumeratedDevice)
DD_JSON_DECLARE_SERIALIZE_TYPE(SingleDisplayConfiguration)
Expand Down
1 change: 1 addition & 0 deletions src/common/include/display_device/json.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
namespace display_device {
extern const std::optional<unsigned int> JSON_COMPACT;

DD_JSON_DECLARE_CONVERTER(EdidData)
DD_JSON_DECLARE_CONVERTER(EnumeratedDevice)
DD_JSON_DECLARE_CONVERTER(EnumeratedDeviceList)
DD_JSON_DECLARE_CONVERTER(SingleDisplayConfiguration)
Expand Down
23 changes: 23 additions & 0 deletions src/common/include/display_device/types.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#pragma once

// system includes
#include <cstdint>
#include <optional>
#include <string>
#include <variant>
Expand Down Expand Up @@ -63,6 +64,27 @@ namespace display_device {
*/
using FloatingPoint = std::variant<double, Rational>;

/**
* @brief Parsed EDID data.
*/
struct EdidData {
std::string m_manufacturer_id {};
std::string m_product_code {};
std::uint32_t m_serial_number {};

/**
* @brief Parse EDID data.
* @param data Data to parse.
* @return Parsed data or empty optional if failed to parse it.
*/
static std::optional<EdidData> parse(const std::vector<std::byte> &data);

/**
* @brief Comparator for strict equality.
*/
friend bool operator==(const EdidData &lhs, const EdidData &rhs);
};

/**
* @brief Enumerated display device information.
*/
Expand All @@ -87,6 +109,7 @@ namespace display_device {
std::string m_device_id {}; /**< A unique device ID used by this API to identify the device. */
std::string m_display_name {}; /**< A logical name representing given by the OS for a display. */
std::string m_friendly_name {}; /**< A human-readable name for the device. */
std::optional<EdidData> m_edid {}; /**< Some basic parsed EDID data. */
std::optional<Info> m_info {}; /**< Additional information about an active display device. */

/**
Expand Down
1 change: 1 addition & 0 deletions src/common/json.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
// clang-format on

namespace display_device {
DD_JSON_DEFINE_CONVERTER(EdidData)
DD_JSON_DEFINE_CONVERTER(EnumeratedDevice)
DD_JSON_DEFINE_CONVERTER(EnumeratedDeviceList)
DD_JSON_DEFINE_CONVERTER(SingleDisplayConfiguration)
Expand Down
3 changes: 2 additions & 1 deletion src/common/json_serializer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ namespace display_device {
DD_JSON_DEFINE_SERIALIZE_STRUCT(Resolution, width, height)
DD_JSON_DEFINE_SERIALIZE_STRUCT(Rational, numerator, denominator)
DD_JSON_DEFINE_SERIALIZE_STRUCT(Point, x, y)
DD_JSON_DEFINE_SERIALIZE_STRUCT(EdidData, manufacturer_id, product_code, serial_number)
DD_JSON_DEFINE_SERIALIZE_STRUCT(EnumeratedDevice::Info, resolution, resolution_scale, refresh_rate, primary, origin_point, hdr_state)
DD_JSON_DEFINE_SERIALIZE_STRUCT(EnumeratedDevice, device_id, display_name, friendly_name, info)
DD_JSON_DEFINE_SERIALIZE_STRUCT(EnumeratedDevice, device_id, display_name, friendly_name, edid, info)
DD_JSON_DEFINE_SERIALIZE_STRUCT(SingleDisplayConfiguration, device_id, device_prep, resolution, refresh_rate, hdr_state)
} // namespace display_device
96 changes: 95 additions & 1 deletion src/common/types.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@
// header include
#include "display_device/types.h"

// system includes
#include <iomanip>
#include <sstream>

// local includes
#include "display_device/logging.h"

namespace {
bool fuzzyCompare(const double lhs, const double rhs) {
return std::abs(lhs - rhs) * 1000000000000. <= std::min(std::abs(lhs), std::abs(rhs));
Expand All @@ -19,6 +26,10 @@ namespace {
}
return false;
}

std::byte operator+(const std::byte &lhs, const std::byte &rhs) {
return std::byte {static_cast<std::uint8_t>(std::to_integer<int>(lhs) + std::to_integer<int>(rhs))};
}
} // namespace

namespace display_device {
Expand All @@ -34,14 +45,97 @@ namespace display_device {
return lhs.m_height == rhs.m_height && lhs.m_width == rhs.m_width;
}

std::optional<EdidData> EdidData::parse(const std::vector<std::byte> &data) {
if (data.empty()) {
return std::nullopt;
}

if (data.size() < 128) {
DD_LOG(warning) << "EDID data size is too small: " << data.size();
return std::nullopt;
}

// ---- Verify fixed header
static const std::vector fixed_header {std::byte {0x00}, std::byte {0xFF}, std::byte {0xFF}, std::byte {0xFF}, std::byte {0xFF}, std::byte {0xFF}, std::byte {0xFF}, std::byte {0x00}};
if (!std::equal(std::begin(fixed_header), std::end(fixed_header), std::begin(data))) {
DD_LOG(warning) << "EDID data does not contain fixed header.";
return std::nullopt;
}

// ---- Verify checksum
{
int sum = 0;
for (std::size_t i = 0; i < 128; ++i) {
sum += static_cast<int>(data[i]);
}

if (sum % 256 != 0) {
DD_LOG(warning) << "EDID checksum verification failed.";
return std::nullopt;
}
}

EdidData edid {};

// ---- Get manufacturer ID (ASCII code A-Z)
{
constexpr std::byte ascii_offset {'@'};

const auto byte_a {data[8]};
const auto byte_b {data[9]};
std::array<char, 3> man_id {};

man_id[0] = static_cast<char>(ascii_offset + ((byte_a & std::byte {0x7C}) >> 2));
man_id[1] = static_cast<char>(ascii_offset + ((byte_a & std::byte {0x03}) << 3) + ((byte_b & std::byte {0xE0}) >> 5));
man_id[2] = static_cast<char>(ascii_offset + (byte_b & std::byte {0x1F}));

for (const char ch : man_id) {
if (ch < 'A' || ch > 'Z') {
DD_LOG(warning) << "EDID manufacturer id is out of range.";
return std::nullopt;
}
}

edid.m_manufacturer_id = {std::begin(man_id), std::end(man_id)};
}

// ---- Product code (HEX representation)
{
std::uint16_t prod_num {0};
prod_num |= std::to_integer<int>(data[10]) << 0;
prod_num |= std::to_integer<int>(data[11]) << 8;

std::stringstream stream;
stream << std::setfill('0') << std::setw(4) << std::hex << std::uppercase << prod_num;
edid.m_product_code = stream.str();
}

// ---- Serial number
{
std::uint32_t serial_num {0};
serial_num |= std::to_integer<int>(data[12]) << 0;
serial_num |= std::to_integer<int>(data[13]) << 8;
serial_num |= std::to_integer<int>(data[14]) << 16;
serial_num |= std::to_integer<int>(data[15]) << 24;

edid.m_serial_number = serial_num;
}

return edid;
}

bool operator==(const EdidData &lhs, const EdidData &rhs) {
return lhs.m_manufacturer_id == rhs.m_manufacturer_id && lhs.m_product_code == rhs.m_product_code && lhs.m_serial_number == rhs.m_serial_number;
}

bool operator==(const EnumeratedDevice::Info &lhs, const EnumeratedDevice::Info &rhs) {
return lhs.m_resolution == rhs.m_resolution && fuzzyCompare(lhs.m_resolution_scale, rhs.m_resolution_scale) &&
fuzzyCompare(lhs.m_refresh_rate, rhs.m_refresh_rate) && lhs.m_primary == rhs.m_primary &&
lhs.m_origin_point == rhs.m_origin_point && lhs.m_hdr_state == rhs.m_hdr_state;
}

bool operator==(const EnumeratedDevice &lhs, const EnumeratedDevice &rhs) {
return lhs.m_device_id == rhs.m_device_id && lhs.m_display_name == rhs.m_display_name && lhs.m_friendly_name == rhs.m_friendly_name && lhs.m_info == rhs.m_info;
return lhs.m_device_id == rhs.m_device_id && lhs.m_display_name == rhs.m_display_name && lhs.m_friendly_name == rhs.m_friendly_name && lhs.m_edid == rhs.m_edid && lhs.m_info == rhs.m_info;
}

bool operator==(const SingleDisplayConfiguration &lhs, const SingleDisplayConfiguration &rhs) {
Expand Down
2 changes: 1 addition & 1 deletion src/windows/include/display_device/windows/types.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ namespace display_device {
*/
struct ValidatedDeviceInfo {
std::string m_device_path {}; /**< Unique device path string. */
std::string m_device_id {}; /**< A device id (made up by us) that is identifies the device. */
std::string m_device_id {}; /**< A device id (made up by us) that identifies the device. */
};

/**
Expand Down
3 changes: 3 additions & 0 deletions src/windows/include/display_device/windows/win_api_layer.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ namespace display_device {
/** For details @see WinApiLayerInterface::getDeviceId */
[[nodiscard]] std::string getDeviceId(const DISPLAYCONFIG_PATH_INFO &path) const override;

/** For details @see WinApiLayerInterface::getEdid */
[[nodiscard]] std::vector<std::byte> getEdid(const DISPLAYCONFIG_PATH_INFO &path) const override;

/** For details @see WinApiLayerInterface::getMonitorDevicePath */
[[nodiscard]] std::string getMonitorDevicePath(const DISPLAYCONFIG_PATH_INFO &path) const override;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,10 @@ namespace display_device {
*
* The accepted solution was to use the "InstanceID" and EDID (just to be
* on the safe side). "InstanceID" is semi-stable, it has some parts that
* change between driver re-installs and it has a part that changes based
* change between driver re-installs, and it has a part that changes based
* on the GPU port that the display is connected to. It is most likely to
* be unique, but since the MS documentation is lacking we are also hashing
* EDID information (contains serial ids, timestamps and etc. that should
* EDID information (contains serial ids, timestamps, etc. that should
* guarantee that identical displays are differentiated like with the
* "ContainerID"). Most importantly this information is stable for the virtual
* displays.
Expand All @@ -84,6 +84,13 @@ namespace display_device {
*/
[[nodiscard]] virtual std::string getDeviceId(const DISPLAYCONFIG_PATH_INFO &path) const = 0;

/**
* @brief Get EDID byte array for the path.
* @param path Path to get the EDID for.
* @return EDID byte array, or an empty array if error has occurred.
*/
[[nodiscard]] virtual std::vector<std::byte> getEdid(const DISPLAYCONFIG_PATH_INFO &path) const = 0;

/**
* @brief Get a string that represents a path from the adapter to the display target.
* @param path Path to get the string for.
Expand All @@ -101,9 +108,9 @@ namespace display_device {
[[nodiscard]] virtual std::string getMonitorDevicePath(const DISPLAYCONFIG_PATH_INFO &path) const = 0;

/**
* @brief Get the user friendly name for the path.
* @param path Path to get user friendly name for.
* @returns User friendly name for the path if available, empty string otherwise.
* @brief Get the user-friendly name for the path.
* @param path Path to get user-friendly name for.
* @returns User-friendly name for the path if available, empty string otherwise.
* @see queryDisplayConfig on how to get paths from the system.
* @note This is usually a monitor name (like "ROG PG279Q") and is most likely taken from EDID.
* @examples
Expand Down
Loading
Loading