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
4 changes: 1 addition & 3 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@ add_subdirectory(common)
if(WIN32)
add_subdirectory(windows)
elseif(APPLE)
add_library(libdisplaydevice_macos_dummy INTERFACE)
add_library(libdisplaydevice::platform ALIAS libdisplaydevice_macos_dummy)
message(WARNING "MacOS is not supported yet.")
add_subdirectory(macos)
elseif(UNIX)
add_library(libdisplaydevice_linux_dummy INTERFACE)
add_library(libdisplaydevice::platform ALIAS libdisplaydevice_linux_dummy)
Expand Down
66 changes: 66 additions & 0 deletions src/common/include/display_device/detail/persistent_state_utils.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/**
* @file src/common/include/display_device/detail/persistent_state_utils.h
* @brief Shared helpers for persistent state wrappers.
*/
#pragma once

// system includes
#include <cstdint>
#include <iterator>
#include <optional>
#include <string_view>
#include <vector>

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

namespace display_device::detail {
/**
* @brief Persist state and update the cached copy after a successful write.
* @tparam State Cached state type.
* @tparam SerializeFn Callable type used to serialize the state.
* @param settings_persistence_api Persistence API used to store or clear state.
* @param cached_state Cached state to compare and update.
* @param state New state to persist.
* @param serialize_state Callable that serializes a state and updates a success flag.
* @param serialize_error_message Error message used when serialization fails.
* @return True if the state was already current or was persisted successfully, false otherwise.
*/
template<typename State, typename SerializeFn>
[[nodiscard]] bool persistState(
SettingsPersistenceInterface &settings_persistence_api,
std::optional<State> &cached_state,
const std::optional<State> &state,
const SerializeFn &serialize_state,
const std::string_view serialize_error_message
) {
if (cached_state == state) {
return true;
}

if (!state) {
if (!settings_persistence_api.clear()) {
return false;
}

cached_state = std::nullopt;
return true;
}

bool success {false};
const auto serialized_state {serialize_state(*state, success)};
if (!success) {
DD_LOG(error) << serialize_error_message << "\n"
<< serialized_state;
return false;
}

if (!settings_persistence_api.store({std::begin(serialized_state), std::end(serialized_state)})) {
return false;
}

cached_state = *state;
return true;
}
} // namespace display_device::detail
108 changes: 108 additions & 0 deletions src/common/include/display_device/detail/settings_state_utils.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/**
* @file src/common/include/display_device/detail/settings_state_utils.h
* @brief Shared helpers for adapting settings state.
*/
#pragma once

// system includes
#include <algorithm>
#include <iterator>
#include <optional>
#include <string>
#include <string_view>
#include <vector>

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

namespace display_device::detail {
/**
* @brief Log messages used while stripping unavailable devices from initial state.
*/
struct InitialStateStripMessages {
std::string_view m_missing_topology; ///< Error logged when no initial topology devices remain.
std::string_view m_missing_primary; ///< Error logged when no usable primary devices remain.
std::string_view m_adapted_state; ///< Warning prefix logged when the initial state is adapted.
};

/**
* @brief Strip unavailable device ids from a topology.
* @tparam Topology Topology container type.
* @param topology Topology to strip.
* @param available_device_ids Device ids currently available.
* @return Topology containing only available device ids.
*/
template<typename Topology>
[[nodiscard]] Topology stripUnavailableTopology(const Topology &topology, const StringSet &available_device_ids) {
Topology stripped_topology;
for (const auto &group : topology) {
std::vector<std::string> stripped_group;
for (const auto &device_id : group) {
if (available_device_ids.contains(device_id)) {
stripped_group.push_back(device_id);
}
}

if (!stripped_group.empty()) {
stripped_topology.push_back(stripped_group);
}
}

return stripped_topology;
}

/**
* @brief Strip unavailable devices from an initial settings state.
* @tparam Initial Initial state type.
* @tparam FormatTopologyFn Callable type used to format topology values for logs.
* @param initial_state Initial state to strip.
* @param available_device_ids Device ids currently available.
* @param primary_device_ids Current primary device ids.
* @param messages Log messages to use for failure and adaptation cases.
* @param format_topology Callable used to format topology values.
* @return Stripped initial state, or empty optional if no usable state remains.
*/
template<typename Initial, typename FormatTopologyFn>
[[nodiscard]] std::optional<Initial> stripInitialState(
const Initial &initial_state,
const StringSet &available_device_ids,
const StringSet &primary_device_ids,
const InitialStateStripMessages &messages,
const FormatTopologyFn &format_topology
) {
const auto stripped_initial_topology {stripUnavailableTopology(initial_state.m_topology, available_device_ids)};

StringSet initial_primary_devices;
std::ranges::set_intersection(
initial_state.m_primary_devices,
available_device_ids,
std::inserter(initial_primary_devices, std::begin(initial_primary_devices))
);

if (stripped_initial_topology.empty()) {
DD_LOG(error) << messages.m_missing_topology;
return std::nullopt;
}

if (initial_primary_devices.empty()) {
initial_primary_devices = primary_device_ids;
if (initial_primary_devices.empty()) {
DD_LOG(error) << messages.m_missing_primary;
return std::nullopt;
}
}

if (initial_state.m_topology != stripped_initial_topology || initial_state.m_primary_devices != initial_primary_devices) {
DD_LOG(warning) << messages.m_adapted_state << "\n"
<< " - topology: " << format_topology(initial_state.m_topology) << " -> " << format_topology(stripped_initial_topology) << "\n"
<< " - primary devices: " << toJson(initial_state.m_primary_devices, JSON_COMPACT) << " -> " << toJson(initial_primary_devices, JSON_COMPACT);
}

return Initial {
stripped_initial_topology,
initial_primary_devices
};
}
} // namespace display_device::detail
66 changes: 66 additions & 0 deletions src/common/include/display_device/display_power_interface.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/**
* @file src/common/include/display_device/display_power_interface.h
* @brief Declarations for display power management interfaces.
*/
#pragma once

// system includes
#include <chrono>
#include <memory>
#include <string>

namespace display_device {
/**
* @brief Scoped guard that keeps a display awake while it is alive.
*
* The guard owns the platform power assertion. Destroying the guard releases
* that assertion. Some platforms attach the assertion to the current thread,
* so callers should destroy the guard on the same thread that created it when
* possible.
*/
class DisplayPowerGuardInterface {
public:
/**
* @brief Default virtual destructor.
*/
virtual ~DisplayPowerGuardInterface() = default;
};

/**
* @brief Cross-platform API for display wake and display-sleep prevention.
*
* This API prepares displays for capture. It does not change display
* topology, primary display selection, resolution, refresh rate, HDR state,
* or any persisted display settings.
*/
class DisplayPowerInterface {
public:
/**
* @brief Default virtual destructor.
*/
virtual ~DisplayPowerInterface() = default;

/**
* @brief Ask the platform to wake a display before detection or capture.
*
* A successful result means the platform accepted the wake request and any
* platform-specific detection that this implementation can perform has
* passed. Some platforms cannot verify that the requested capture selector
* is awake, so callers should still retry their own capture-target
* enumeration after this method succeeds.
*
* @param display_name Platform capture selector returned by SettingsManagerInterface::getDisplayName.
* @param timeout Maximum time to wait before the caller retries capture-target detection.
* @returns True if the wake request succeeded and platform-specific detection passed, false otherwise.
*/
[[nodiscard]] virtual bool wakeDisplay(const std::string &display_name, std::chrono::milliseconds timeout) = 0;

/**
* @brief Keep displays awake until the returned guard is destroyed.
*
* @param reason Short human-readable reason for the platform power assertion.
* @returns A guard that owns the assertion, or nullptr if the assertion could not be created.
*/
[[nodiscard]] virtual std::unique_ptr<DisplayPowerGuardInterface> keepDisplayAwake(const std::string &reason) = 0;
};
} // namespace display_device
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,9 @@ namespace display_device {
[[nodiscard]] virtual EnumeratedDeviceList enumAvailableDevices() const = 0;

/**
* @brief Get display name associated with the device.
* @brief Get the platform-specific display name associated with the device.
* @param device_id A device to get display name for.
* @returns A display name for the device, or an empty string if the device is inactive or not found.
* @returns A display name or capture selector for the device, or an empty string if the device is inactive or not found.
* Empty string can also be returned if an error has occurred.
* @examples
* const std::string device_id { "MY_DEVICE_ID" };
Expand Down
2 changes: 1 addition & 1 deletion src/common/include/display_device/types.h
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,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_display_name {}; ///< Platform-specific display name or capture selector for the device.
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
26 changes: 26 additions & 0 deletions src/macos/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# A global identifier for the library
set(MODULE libdisplaydevice_macos)
set(MODULE_ALIAS libdisplaydevice::platform)

# Globing headers (so that they appear in some IDEs) and sources
file(GLOB HEADER_LIST CONFIGURE_DEPENDS "include/display_device/macos/*.h")
file(GLOB HEADER_DETAIL_LIST CONFIGURE_DEPENDS "include/display_device/macos/detail/*.h")
file(GLOB SOURCE_LIST CONFIGURE_DEPENDS "*.cpp")

# Automatic library - will be static or dynamic based on user setting
add_library(${MODULE} ${HEADER_LIST} ${HEADER_DETAIL_LIST} ${SOURCE_LIST})
add_library(${MODULE_ALIAS} ALIAS ${MODULE})

# Provide the includes together with this library
target_include_directories(${MODULE} PUBLIC include)

# Additional external libraries
include(Json_DD)

# Link the additional libraries
target_link_libraries(${MODULE} PRIVATE
libdisplaydevice::common
nlohmann_json::nlohmann_json
"-framework CoreFoundation"
"-framework CoreGraphics"
"-framework IOKit")
Loading
Loading