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/types.h
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ namespace display_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.
bool m_is_internal {}; ///< Positively identified built-in panel; false also covers unknown connectors.

/**
* @brief Comparator for strict equality.
Expand Down
29 changes: 28 additions & 1 deletion src/common/json_serializer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,33 @@ namespace display_device {
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, edid, info)

/**
* @brief Serialize an enumerated display, including its internal-panel classification.
* @param nlohmann_json_j JSON output object.
* @param nlohmann_json_t Device to serialize.
*/
void to_json(nlohmann::json &nlohmann_json_j, const EnumeratedDevice &nlohmann_json_t) {
DD_JSON_TO(device_id)
DD_JSON_TO(display_name)
DD_JSON_TO(friendly_name)
DD_JSON_TO(edid)
DD_JSON_TO(info)
DD_JSON_TO(is_internal)
}

/**
* @brief Deserialize a display, treating missing internal-panel information as unknown.
* @param nlohmann_json_j JSON input object.
* @param nlohmann_json_t Device to populate.
*/
void from_json(const nlohmann::json &nlohmann_json_j, EnumeratedDevice &nlohmann_json_t) {
DD_JSON_FROM(device_id)
DD_JSON_FROM(display_name)
DD_JSON_FROM(friendly_name)
DD_JSON_FROM(edid)
DD_JSON_FROM(info)
nlohmann_json_t.m_is_internal = nlohmann_json_j.value("is_internal", false);
}
DD_JSON_DEFINE_SERIALIZE_STRUCT(SingleDisplayConfiguration, device_id, device_prep, resolution, refresh_rate, hdr_state)
} // namespace display_device
11 changes: 11 additions & 0 deletions src/windows/include/display_device/windows/settings_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ namespace display_device {

/**
* @brief Try to revert the modified settings.
* If a device from the modified topology is no longer available, only the settings
* of the remaining devices are reverted and the persisted record is left unchanged.
* @param current_topology Topology before this method is called.
* @param system_settings_touched Indicates whether a "write" operation could have been performed on the OS.
* @param switched_topology [Optional] Indicates whether the current topology was switched to revert settings.
Expand Down Expand Up @@ -148,6 +150,15 @@ namespace display_device {
*/
[[nodiscard]] RevertResult revertModifiedPrimaryDevice(const SingleDisplayConfigState::Modified &modified_state, DdGuardFn &guard_fn, bool &system_settings_touched);

/**
* @brief Recover to surviving original displays or a built-in panel after an undock.
* Only devices activated by this session may be removed. The original recovery
* record remains intact on failure; a visible replacement is verified first.
* @param current_topology Active topology before attempting to restore the saved layout.
* @return True when the replacement topology has been applied and verified.
*/
[[nodiscard]] bool recoverMissingTopology(const ActiveTopology &current_topology);

private:
std::shared_ptr<WinDisplayDeviceInterface> m_dd_api;
std::shared_ptr<AudioContextInterface> m_audio_context_api;
Expand Down
10 changes: 10 additions & 0 deletions src/windows/include/display_device/windows/settings_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,16 @@ namespace display_device::win_utils {
*/
StringSet flattenTopology(const ActiveTopology &topology);

/**
* @brief Get the ids of the enumerated devices.
* @param devices Enumerated devices.
* @return Device ids of the enumerated devices.
* @examples
* const auto device_ids { getDeviceIds(win_dd.enumAvailableDevices()) };
* @examples_end
*/
StringSet getDeviceIds(const EnumeratedDeviceList &devices);

/**
* @brief Create extended topology from all the available devices.
* @param win_dd Interface for interacting with the OS.
Expand Down
1 change: 1 addition & 0 deletions src/windows/include/display_device/windows/types.h
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ namespace display_device {
DeviceDisplayModeMap m_original_modes {}; ///< Original display modes before modification.
HdrStateMap m_original_hdr_states {}; ///< Original HDR states before modification.
std::string m_original_primary_device {}; ///< Original primary device before modification.
std::string m_interim_primary_device {}; ///< Primary device to restore while the original one is unavailable.

/**
* @brief Check if the changed topology has any other modifications.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@ namespace display_device {
public:
/**
* Default constructor for the class.
* @param save_to_database Whether CCD changes update Windows' saved display layout.
* @param w_api A pointer to the Windows API layer. Will throw on nullptr!
*/
explicit WinDisplayDevice(std::shared_ptr<WinApiLayerInterface> w_api);
explicit WinDisplayDevice(std::shared_ptr<WinApiLayerInterface> w_api, bool save_to_database = true);

/**
* @copydoc WinDisplayDeviceInterface::isApiAccessAvailable
Expand Down Expand Up @@ -90,5 +91,6 @@ namespace display_device {

private:
std::shared_ptr<WinApiLayerInterface> m_w_api;
bool m_save_to_database;
};
} // namespace display_device
30 changes: 29 additions & 1 deletion src/windows/json_serializer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,35 @@ namespace display_device {
// Structs
DD_JSON_DEFINE_SERIALIZE_STRUCT(DisplayMode, resolution, refresh_rate)
DD_JSON_DEFINE_SERIALIZE_STRUCT(SingleDisplayConfigState::Initial, topology, primary_devices)
DD_JSON_DEFINE_SERIALIZE_STRUCT(SingleDisplayConfigState::Modified, topology, original_modes, original_hdr_states, original_primary_device)

/**
* @brief Serialize the modified state, including the interim primary device when there is one.
* @param nlohmann_json_j JSON output object.
* @param nlohmann_json_t Modified state to serialize.
*/
void to_json(nlohmann::json &nlohmann_json_j, const SingleDisplayConfigState::Modified &nlohmann_json_t) {
DD_JSON_TO(topology)
DD_JSON_TO(original_modes)
DD_JSON_TO(original_hdr_states)
DD_JSON_TO(original_primary_device)
if (!nlohmann_json_t.m_interim_primary_device.empty()) {
DD_JSON_TO(interim_primary_device)
}
}

/**
* @brief Deserialize the modified state, treating a missing interim primary device as none.
* @param nlohmann_json_j JSON input object.
* @param nlohmann_json_t Modified state to populate.
*/
void from_json(const nlohmann::json &nlohmann_json_j, SingleDisplayConfigState::Modified &nlohmann_json_t) {
DD_JSON_FROM(topology)
DD_JSON_FROM(original_modes)
DD_JSON_FROM(original_hdr_states)
DD_JSON_FROM(original_primary_device)
nlohmann_json_t.m_interim_primary_device = nlohmann_json_j.value("interim_primary_device", std::string {});
}

DD_JSON_DEFINE_SERIALIZE_STRUCT(SingleDisplayConfigState, initial, modified)
DD_JSON_DEFINE_SERIALIZE_STRUCT(WinWorkarounds, hdr_blank_delay)
} // namespace display_device
52 changes: 48 additions & 4 deletions src/windows/settings_manager_apply.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,27 @@ namespace display_device {
void noopFn() {
// Intentionally empty guard callback.
}

/**
* @brief Split off the cached entries of devices that are not part of the topology being applied.
*
* Such devices, for example an unplugged display, cannot be restored now, so their entries must be carried over untouched.
* @param entries Cached per-device entries. The entries of inactive devices are removed.
* @param active Device IDs of the topology being applied.
* @return The removed entries.
*/
template<class Entries>
Entries takeInactive(Entries &entries, const StringSet &active) {
Entries inactive;
std::erase_if(entries, [&active, &inactive](const auto &entry) {
if (active.contains(entry.first)) {
return false;
}
inactive.insert(entry);
return true;
});
return inactive;
}
} // namespace

SettingsManager::ApplyResult SettingsManager::applySettings(const SingleDisplayConfiguration &config) {
Expand Down Expand Up @@ -206,7 +227,18 @@ namespace display_device {

bool SettingsManager::preparePrimaryDevice(const SingleDisplayConfiguration &config, const std::string &device_to_configure, DdGuardFn &guard_fn, SingleDisplayConfigState &new_state, bool &system_settings_touched) {
const auto &cached_state {m_persistence_state->getState()};
const auto cached_primary_device {cached_state ? cached_state->m_modified.m_original_primary_device : std::string {}};
auto cached_primary_device {cached_state ? cached_state->m_modified.m_original_primary_device : std::string {}};
const auto cached_interim_device {cached_state ? cached_state->m_modified.m_interim_primary_device : std::string {}};
std::string pending_primary_device;
if (const auto active_devices {win_utils::flattenTopology(new_state.m_modified.m_topology)}; !cached_primary_device.empty() && !active_devices.contains(cached_primary_device)) {
// The original primary device is inactive (e.g. unplugged), so it can only be restored once it returns.
// Until then, the device that was primary before this happened is the one to go back to.
pending_primary_device = std::move(cached_primary_device);
cached_primary_device = cached_interim_device;
if (!active_devices.contains(cached_primary_device)) {
cached_primary_device.clear();
}
}
const bool ensure_primary {config.m_device_prep == SingleDisplayConfiguration::DevicePreparation::EnsurePrimary};
const bool might_need_to_restore {!cached_primary_device.empty()};

Expand Down Expand Up @@ -246,7 +278,12 @@ namespace display_device {
}

// Here we preserve the data from persistence (unless there's none) as in the end that is what we want to go back to.
new_state.m_modified.m_original_primary_device = original_primary_device;
if (pending_primary_device.empty()) {
new_state.m_modified.m_original_primary_device = original_primary_device;
} else {
new_state.m_modified.m_original_primary_device = pending_primary_device;
new_state.m_modified.m_interim_primary_device = original_primary_device;
}
return true;
}

Expand All @@ -255,12 +292,14 @@ namespace display_device {
return false;
}

new_state.m_modified.m_original_primary_device = pending_primary_device;
return true;
}

bool SettingsManager::prepareDisplayModes(const SingleDisplayConfiguration &config, const std::string &device_to_configure, const StringSet &additional_devices_to_configure, DdGuardFn &guard_fn, SingleDisplayConfigState &new_state, bool &system_settings_touched) {
const auto &cached_state {m_persistence_state->getState()};
const auto cached_display_modes {cached_state ? cached_state->m_modified.m_original_modes : DeviceDisplayModeMap {}};
auto cached_display_modes {cached_state ? cached_state->m_modified.m_original_modes : DeviceDisplayModeMap {}};
const auto pending_display_modes {takeInactive(cached_display_modes, win_utils::flattenTopology(new_state.m_modified.m_topology))};
const bool change_required {config.m_resolution || config.m_refresh_rate};
const bool might_need_to_restore {!cached_display_modes.empty()};

Expand Down Expand Up @@ -306,6 +345,7 @@ namespace display_device {

// Here we preserve the data from persistence (unless there's none) as in the end that is what we want to go back to.
new_state.m_modified.m_original_modes = original_display_modes;
new_state.m_modified.m_original_modes.insert(pending_display_modes.begin(), pending_display_modes.end());
return true;
}

Expand All @@ -314,12 +354,14 @@ namespace display_device {
return false;
}

new_state.m_modified.m_original_modes = pending_display_modes;
return true;
}

[[nodiscard]] bool SettingsManager::prepareHdrStates(const SingleDisplayConfiguration &config, const std::string &device_to_configure, const StringSet &additional_devices_to_configure, DdGuardFn &guard_fn, SingleDisplayConfigState &new_state, bool &system_settings_touched) {
const auto &cached_state {m_persistence_state->getState()};
const auto cached_hdr_states {cached_state ? cached_state->m_modified.m_original_hdr_states : HdrStateMap {}};
auto cached_hdr_states {cached_state ? cached_state->m_modified.m_original_hdr_states : HdrStateMap {}};
const auto pending_hdr_states {takeInactive(cached_hdr_states, win_utils::flattenTopology(new_state.m_modified.m_topology))};
const bool change_required {config.m_hdr_state};
const bool might_need_to_restore {!cached_hdr_states.empty()};

Expand Down Expand Up @@ -360,6 +402,7 @@ namespace display_device {

// Here we preserve the data from persistence (unless there's none) as in the end that is what we want to go back to.
new_state.m_modified.m_original_hdr_states = original_hdr_states;
new_state.m_modified.m_original_hdr_states.insert(pending_hdr_states.begin(), pending_hdr_states.end());
return true;
}

Expand All @@ -368,6 +411,7 @@ namespace display_device {
return false;
}

new_state.m_modified.m_original_hdr_states = pending_hdr_states;
return true;
}
} // namespace display_device
Loading
Loading