Skip to content

Commit

Permalink
feat: Add primary device handling logic (#52)
Browse files Browse the repository at this point in the history
  • Loading branch information
FrogTheFrog authored Jul 10, 2024
1 parent 7c494d7 commit 2dcf5f7
Show file tree
Hide file tree
Showing 10 changed files with 425 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ namespace display_device {
Ok,
ApiTemporarilyUnavailable,
DevicePrepFailed,
PrimaryDevicePrepFailed,
PersistenceSaveFailed,
};

Expand Down
11 changes: 11 additions & 0 deletions src/windows/include/displaydevice/windows/settingsmanager.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,17 @@ namespace display_device {
[[nodiscard]] std::optional<std::tuple<SingleDisplayConfigState, std::string, std::set<std::string>>>
prepareTopology(const SingleDisplayConfiguration &config, const ActiveTopology &topology_before_changes, bool &release_context);

/**
* @brief Changes or restores the primary device based on the cached state, new state and configuration.
* @param config Configuration to be used for preparing primary device.
* @param device_to_configure The main device to be used for preparation.
* @param guard_fn Reference to the guard function which will be set to restore original state (if needed) in case something else fails down the line.
* @param new_state Reference to the new state which is to be updated accordingly.
* @return True if no errors have occured, false otherwise.
*/
[[nodiscard]] bool
preparePrimaryDevice(const SingleDisplayConfiguration &config, const std::string &device_to_configure, DdGuardFn &guard_fn, SingleDisplayConfigState &new_state);

/**
* @brief Try to revert the modified settings.
* @returns True on success, false otherwise.
Expand Down
33 changes: 33 additions & 0 deletions src/windows/include/displaydevice/windows/settingsutils.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,22 @@ namespace display_device::win_utils {
std::set<std::string>
flattenTopology(const ActiveTopology &topology);

/**
* @brief Get one primary device from the provided topology.
* @param win_dd Interface for interacting with the OS.
* @param topology Topology to be searched.
* @return Id of a primary device or an empty string if not found or an error has occured.
*
* EXAMPLES:
* ```cpp
* const WinDisplayDeviceInterface* iface = getIface(...);
* const ActiveTopology topology { { "DeviceId1", "DeviceId2" }, { "DeviceId3" } };
* const auto primary_device_id { getPrimaryDevice(*iface, topology) };
* ```
*/
std::string
getPrimaryDevice(WinDisplayDeviceInterface &win_dd, const ActiveTopology &topology);

/**
* @brief Compute the new intial state from arbitrary data.
* @param prev_state Previous initial state if available.
Expand Down Expand Up @@ -128,6 +144,23 @@ namespace display_device::win_utils {
DdGuardFn
primaryGuardFn(WinDisplayDeviceInterface &win_dd, const ActiveTopology &topology);

/**
* @brief Make guard function for the primary display.
* @param win_dd Interface for interacting with the OS.
* @param primary_device Primary device to restore when the guard is executed.
* @return Function that once called will try to revert primary display to the one that was provided.
*
* EXAMPLES:
* ```cpp
* WinDisplayDeviceInterface* iface = getIface(...);
* const std::string prev_primary_device { "MyDeviceId" };
*
* boost::scope::scope_exit guard { primaryGuardFn(*iface, prev_primary_device) };
* ```
*/
DdGuardFn
primaryGuardFn(WinDisplayDeviceInterface &win_dd, const std::string &primary_device);

/**
* @brief Make guard function for the HDR states.
* @param win_dd Interface for interacting with the OS.
Expand Down
75 changes: 73 additions & 2 deletions src/windows/settingsmanagerapply.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,15 @@
#include "displaydevice/windows/settingsutils.h"

namespace display_device {
namespace {
/**
* @brief Function that does nothing.
*/
void
noopFn() {
}
} // namespace

SettingsManager::ApplyResult
SettingsManager::applySettings(const SingleDisplayConfiguration &config) {
const auto api_access { m_dd_api->isApiAccessAvailable() };
Expand Down Expand Up @@ -57,13 +66,19 @@ namespace display_device {
// Error already logged
return ApplyResult::DevicePrepFailed;
}
const auto &[new_state, device_to_configure, additional_devices_to_configure] = *prepped_topology_data;
auto [new_state, device_to_configure, additional_devices_to_configure] = *prepped_topology_data;

DdGuardFn primary_guard_fn { noopFn };
boost::scope::scope_exit<DdGuardFn &> primary_guard { primary_guard_fn };
if (!preparePrimaryDevice(config, device_to_configure, primary_guard_fn, new_state)) {
// Error already logged
return ApplyResult::PrimaryDevicePrepFailed;
}

// TODO:
//
// Other device handling goes here that will use device_to_configure and additional_devices_to_configure:
//
// - handle primary device
// - handle display modes
// - handle HDR (need to replicate the HDR bug and find the best place for workaround)
//
Expand All @@ -82,6 +97,7 @@ namespace display_device {

// Disable all guards before returning
topology_prep_guard.set_active(false);
primary_guard.set_active(false);
return ApplyResult::Ok;
}

Expand Down Expand Up @@ -170,4 +186,59 @@ namespace display_device {
new_state.m_modified.m_topology = new_topology;
return std::make_tuple(new_state, device_to_configure, additional_devices_to_configure);
}

bool
SettingsManager::preparePrimaryDevice(const SingleDisplayConfiguration &config, const std::string &device_to_configure, DdGuardFn &guard_fn, SingleDisplayConfigState &new_state) {
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 {} };
const bool ensure_primary { config.m_device_prep == SingleDisplayConfiguration::DevicePreparation::EnsurePrimary };
const bool might_need_to_restore { !cached_primary_device.empty() };

std::string current_primary_device;
if (ensure_primary || might_need_to_restore) {
current_primary_device = win_utils::getPrimaryDevice(*m_dd_api, new_state.m_modified.m_topology);
if (current_primary_device.empty()) {
DD_LOG(error) << "Failed to get primary device for the topology! Searched topology:\n"
<< toJson(new_state.m_modified.m_topology);
return false;
}
}

const auto try_change { [&](const std::string &new_device, const auto info_preamble, const auto error_log) {
if (current_primary_device != new_device) {
DD_LOG(info) << info_preamble << toJson(new_device);
if (!m_dd_api->setAsPrimary(new_device)) {
DD_LOG(error) << error_log;
return false;
}

guard_fn = win_utils::primaryGuardFn(*m_dd_api, current_primary_device);
}

return true;
} };

if (ensure_primary) {
const auto original_primary_device { cached_primary_device.empty() ? current_primary_device : cached_primary_device };
const auto &new_primary_device { device_to_configure };

if (!try_change(new_primary_device, "Changing primary display to: ", "Failed to apply new configuration, because a new primary device could not be set!")) {
// Error already logged
return false;
}

// 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;
return true;
}

if (might_need_to_restore) {
if (!try_change(cached_primary_device, "Changing primary display back to: ", "Failed to restore original primary device!")) {
// Error already logged
return false;
}
}

return true;
}
} // namespace display_device
27 changes: 18 additions & 9 deletions src/windows/settingsmanagerrevert.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,15 @@
#include "displaydevice/windows/settingsutils.h"

namespace display_device {
namespace {
/**
* @brief Function that does nothing.
*/
void
noopFn() {
}
} // namespace

bool
SettingsManager::revertSettings() {
const auto &cached_state { m_persistence_state->getState() };
Expand Down Expand Up @@ -84,40 +93,40 @@ namespace display_device {
return false;
}

DdGuardFn hdr_guard_fn;
boost::scope::scope_exit<DdGuardFn &> hdr_guard { hdr_guard_fn, false };
DdGuardFn hdr_guard_fn { noopFn };
boost::scope::scope_exit<DdGuardFn &> hdr_guard { hdr_guard_fn };
if (!cached_state->m_modified.m_original_hdr_states.empty()) {
hdr_guard_fn = win_utils::hdrStateGuardFn(*m_dd_api, cached_state->m_modified.m_topology);
hdr_guard.set_active(true);
DD_LOG(info) << "Trying to change back the HDR states to:\n"
<< toJson(cached_state->m_modified.m_original_hdr_states);
if (!m_dd_api->setHdrStates(cached_state->m_modified.m_original_hdr_states)) {
// Error already logged
hdr_guard.set_active(false);
return false;
}
}

DdGuardFn mode_guard_fn;
boost::scope::scope_exit<DdGuardFn &> mode_guard { mode_guard_fn, false };
DdGuardFn mode_guard_fn { noopFn };
boost::scope::scope_exit<DdGuardFn &> mode_guard { mode_guard_fn };
if (!cached_state->m_modified.m_original_modes.empty()) {
mode_guard_fn = win_utils::modeGuardFn(*m_dd_api, cached_state->m_modified.m_topology);
mode_guard.set_active(true);
DD_LOG(info) << "Trying to change back the display modes to:\n"
<< toJson(cached_state->m_modified.m_original_modes);
if (!m_dd_api->setDisplayModes(cached_state->m_modified.m_original_modes)) {
// Error already logged
mode_guard.set_active(false);
return false;
}
}

DdGuardFn primary_guard_fn;
boost::scope::scope_exit<DdGuardFn &> primary_guard { primary_guard_fn, false };
DdGuardFn primary_guard_fn { noopFn };
boost::scope::scope_exit<DdGuardFn &> primary_guard { primary_guard_fn };
if (!cached_state->m_modified.m_original_primary_device.empty()) {
primary_guard_fn = win_utils::primaryGuardFn(*m_dd_api, cached_state->m_modified.m_topology);
primary_guard.set_active(true);
DD_LOG(info) << "Trying to change back the original primary device to: " << toJson(cached_state->m_modified.m_original_primary_device);
if (!m_dd_api->setAsPrimary(cached_state->m_modified.m_original_primary_device)) {
// Error already logged
primary_guard.set_active(false);
return false;
}
}
Expand Down
24 changes: 16 additions & 8 deletions src/windows/settingsutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,18 @@ namespace display_device::win_utils {
return flattened_topology;
}

std::string
getPrimaryDevice(WinDisplayDeviceInterface &win_dd, const ActiveTopology &topology) {
const auto flat_topology { flattenTopology(topology) };
for (const auto &device_id : flat_topology) {
if (win_dd.isPrimary(device_id)) {
return device_id;
}
}

return {};
}

std::optional<SingleDisplayConfigState::Initial>
computeInitialState(const std::optional<SingleDisplayConfigState::Initial> &prev_state, const ActiveTopology &topology_before_changes, const EnumeratedDeviceList &devices) {
// We first need to determine the "initial" state that will be used when reverting
Expand Down Expand Up @@ -279,15 +291,11 @@ namespace display_device::win_utils {

DdGuardFn
primaryGuardFn(WinDisplayDeviceInterface &win_dd, const ActiveTopology &topology) {
std::string primary_device {};
const auto flat_topology { flattenTopology(topology) };
for (const auto &device_id : flat_topology) {
if (win_dd.isPrimary(device_id)) {
primary_device = device_id;
break;
}
}
return primaryGuardFn(win_dd, getPrimaryDevice(win_dd, topology));
}

DdGuardFn
primaryGuardFn(WinDisplayDeviceInterface &win_dd, const std::string &primary_device) {
DD_LOG(debug) << "Got primary device in primaryGuardFn:\n"
<< toJson(primary_device);
return [&win_dd, primary_device]() {
Expand Down
Loading

0 comments on commit 2dcf5f7

Please sign in to comment.