Skip to content

Commit

Permalink
feat: Add display mode handling logic (#53)
Browse files Browse the repository at this point in the history
  • Loading branch information
FrogTheFrog authored Jul 10, 2024
1 parent 2dcf5f7 commit 239f0cb
Show file tree
Hide file tree
Showing 20 changed files with 564 additions and 72 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ namespace display_device {
ApiTemporarilyUnavailable,
DevicePrepFailed,
PrimaryDevicePrepFailed,
DisplayModePrepFailed,
PersistenceSaveFailed,
};

Expand Down
6 changes: 3 additions & 3 deletions src/common/include/displaydevice/types.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ namespace display_device {
*/
struct Info {
Resolution m_resolution {}; /**< Resolution of an active device. */
float m_resolution_scale {}; /**< Resolution scaling of an active device. */
float m_refresh_rate {}; /**< Refresh rate of an active device. */
double m_resolution_scale {}; /**< Resolution scaling of an active device. */
double m_refresh_rate {}; /**< Refresh rate of an active device. */
bool m_primary {}; /**< Indicates whether the device is a primary display. */
Point m_origin_point {}; /**< A starting point of the display. */
std::optional<HdrState> m_hdr_state {}; /**< HDR of an active device. */
Expand Down Expand Up @@ -101,7 +101,7 @@ namespace display_device {
std::string m_device_id {}; /**< Device to perform configuration for (can be empty if primary device should be used). */
DevicePreparation m_device_prep {}; /**< Instruction on how to prepare device. */
std::optional<Resolution> m_resolution {}; /**< Resolution to configure. */
std::optional<float> m_refresh_rate {}; /**< Refresh rate to configure. */
std::optional<double> m_refresh_rate {}; /**< Refresh rate to configure. */
std::optional<HdrState> m_hdr_state {}; /**< HDR state to configure (if supported by the display). */

/**
Expand Down
4 changes: 2 additions & 2 deletions src/common/types.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@

namespace {
bool
fuzzyCompare(const float lhs, const float rhs) {
return std::abs(lhs - rhs) * 100000.f <= std::min(std::abs(lhs), std::abs(rhs));
fuzzyCompare(const double lhs, const double rhs) {
return std::abs(lhs - rhs) * 1000000000000. <= std::min(std::abs(lhs), std::abs(rhs));
}
} // namespace

Expand Down
12 changes: 12 additions & 0 deletions src/windows/include/displaydevice/windows/settingsmanager.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,18 @@ namespace display_device {
[[nodiscard]] bool
preparePrimaryDevice(const SingleDisplayConfiguration &config, const std::string &device_to_configure, DdGuardFn &guard_fn, SingleDisplayConfigState &new_state);

/**
* @brief Changes or restores the display modes based on the cached state, new state and configuration.
* @param config Configuration to be used for preparing display modes.
* @param device_to_configure The main device to be used for preparation.
* @param additional_devices_to_configure Additional devices that should be configured.
* @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
prepareDisplayModes(const SingleDisplayConfiguration &config, const std::string &device_to_configure, const std::set<std::string> &additional_devices_to_configure, DdGuardFn &guard_fn, SingleDisplayConfigState &new_state);

/**
* @brief Try to revert the modified settings.
* @returns True on success, false otherwise.
Expand Down
37 changes: 36 additions & 1 deletion src/windows/include/displaydevice/windows/settingsutils.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ namespace display_device::win_utils {
stripInitialState(const SingleDisplayConfigState::Initial &initial_state, const EnumeratedDeviceList &devices);

/**
* @brief Compute new topology from arbitrary.
* @brief Compute new topology from arbitrary data.
* @param device_prep Specify how to to compute the new topology.
* @param configuring_primary_devices Specify whether the `device_to_configure` was unspecified (primary device was selected).
* @param device_to_configure Main device to be configured.
Expand All @@ -99,6 +99,24 @@ namespace display_device::win_utils {
const std::string &device_id,
const SingleDisplayConfigState::Initial &initial_state);

/**
* @brief Compute new display modes from arbitrary data.
* @param resolution Specify resolution that should be used to override the original modes.
* @param refresh_rate Specify refresh rate that should be used to override the original modes.
* @param configuring_primary_devices Specify whether the `device_to_configure` was unspecified (primary device was selected).
* @param device_to_configure Main device to be configured.
* @param additional_devices_to_configure Additional devices that belong to the same group as `device_to_configure`.
* @param original_modes Display modes to be used as a base onto which changes are made.
* @return New display modes that should be set.
*/
DeviceDisplayModeMap
computeNewDisplayModes(const std::optional<Resolution> &resolution,
const std::optional<double> &refresh_rate,
bool configuring_primary_devices,
const std::string &device_to_configure,
const std::set<std::string> &additional_devices_to_configure,
const DeviceDisplayModeMap &original_modes);

/**
* @brief Make guard function for the topology.
* @param win_dd Interface for interacting with the OS.
Expand Down Expand Up @@ -129,6 +147,23 @@ namespace display_device::win_utils {
DdGuardFn
modeGuardFn(WinDisplayDeviceInterface &win_dd, const ActiveTopology &topology);

/**
* @brief Make guard function for the display modes.
* @param win_dd Interface for interacting with the OS.
* @param modes Display modes to restore when the guard is executed.
* @return Function that once called will try to revert display modes to the ones that were provided.
*
* EXAMPLES:
* ```cpp
* WinDisplayDeviceInterface* iface = getIface(...);
* const DeviceDisplayModeMap modes { };
*
* boost::scope::scope_exit guard { modeGuardFn(*iface, modes) };
* ```
*/
DdGuardFn
modeGuardFn(WinDisplayDeviceInterface &win_dd, const DeviceDisplayModeMap &modes);

/**
* @brief Make guard function for the primary display.
* @param win_dd Interface for interacting with the OS.
Expand Down
6 changes: 6 additions & 0 deletions src/windows/include/displaydevice/windows/types.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,12 @@ namespace display_device {
unsigned int m_numerator {};
unsigned int m_denominator {};

/**
* @brief Contruct rational struct from double precission floating point.
*/
static Rational
fromFloatingPoint(double value);

/**
* @brief Comparator for strict equality.
*/
Expand Down
2 changes: 1 addition & 1 deletion src/windows/include/displaydevice/windows/winapilayer.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ namespace display_device {
setHdrState(const DISPLAYCONFIG_PATH_INFO &path, HdrState state) override;

/** For details @see WinApiLayerInterface::getDisplayScale */
[[nodiscard]] std::optional<float>
[[nodiscard]] std::optional<double>
getDisplayScale(const std::string &display_name, const DISPLAYCONFIG_SOURCE_MODE &source_mode) const override;
};
} // namespace display_device
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ namespace display_device {
* const auto scale = iface->getDisplayScale(iface->getDisplayName(path), source_mode);
* ```
*/
[[nodiscard]] virtual std::optional<float>
[[nodiscard]] virtual std::optional<double>
getDisplayScale(const std::string &display_name, const DISPLAYCONFIG_SOURCE_MODE &source_mode) const = 0;
};
} // namespace display_device
65 changes: 64 additions & 1 deletion src/windows/settingsmanagerapply.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,17 @@ namespace display_device {
return ApplyResult::PrimaryDevicePrepFailed;
}

DdGuardFn mode_guard_fn { noopFn };
boost::scope::scope_exit<DdGuardFn &> mode_guard { mode_guard_fn };
if (!prepareDisplayModes(config, device_to_configure, additional_devices_to_configure, mode_guard_fn, new_state)) {
// Error already logged
return ApplyResult::DisplayModePrepFailed;
}

// TODO:
//
// Other device handling goes here that will use device_to_configure and additional_devices_to_configure:
//
// - handle display modes
// - handle HDR (need to replicate the HDR bug and find the best place for workaround)
//

Expand All @@ -98,6 +104,7 @@ namespace display_device {
// Disable all guards before returning
topology_prep_guard.set_active(false);
primary_guard.set_active(false);
mode_guard.set_active(false);
return ApplyResult::Ok;
}

Expand Down Expand Up @@ -241,4 +248,60 @@ namespace display_device {

return true;
}

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

DeviceDisplayModeMap current_display_modes;
if (change_required || might_need_to_restore) {
current_display_modes = m_dd_api->getCurrentDisplayModes(win_utils::flattenTopology(new_state.m_modified.m_topology));
if (current_display_modes.empty()) {
DD_LOG(error) << "Failed to get current display modes!";
return false;
}
}

const auto try_change { [&](const DeviceDisplayModeMap &new_modes, const auto info_preamble, const auto error_log) {
if (current_display_modes != new_modes) {
DD_LOG(info) << info_preamble << toJson(new_modes);
if (!m_dd_api->setDisplayModes(new_modes)) {
DD_LOG(error) << error_log;
return false;
}

guard_fn = win_utils::modeGuardFn(*m_dd_api, current_display_modes);
}

return true;
} };

if (change_required) {
const bool configuring_primary_devices { config.m_device_id.empty() };
const auto original_display_modes { cached_display_modes.empty() ? current_display_modes : cached_display_modes };
const auto new_display_modes { win_utils::computeNewDisplayModes(config.m_resolution,
config.m_refresh_rate, configuring_primary_devices, device_to_configure, additional_devices_to_configure, original_display_modes) };

if (!try_change(new_display_modes, "Changing display modes to: ", "Failed to apply new configuration, because new display modes 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_modes = original_display_modes;
return true;
}

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

return true;
}
} // namespace display_device
56 changes: 46 additions & 10 deletions src/windows/settingsutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -197,15 +197,13 @@ namespace display_device::win_utils {

return ActiveTopology { { device_to_configure } };
}
// DevicePrep::EnsureActive || DevicePrep::EnsurePrimary
else {
// The device needs to be active at least.
if (!flattenTopology(initial_topology).contains(device_to_configure)) {
// Create an extended topology as it's probably what makes sense the most...
ActiveTopology new_topology { initial_topology };
new_topology.push_back({ device_to_configure });
return new_topology;
}

// The device needs to be active at least for `DevicePrep::EnsureActive || DevicePrep::EnsurePrimary`.
if (!flattenTopology(initial_topology).contains(device_to_configure)) {
// Create an extended topology as it's probably what makes sense the most...
ActiveTopology new_topology { initial_topology };
new_topology.push_back({ device_to_configure });
return new_topology;
}
}

Expand Down Expand Up @@ -264,6 +262,40 @@ namespace display_device::win_utils {
return std::make_tuple(new_topology, device_to_configure, additional_devices_to_configure);
}

DeviceDisplayModeMap
computeNewDisplayModes(const std::optional<Resolution> &resolution, const std::optional<double> &refresh_rate, const bool configuring_primary_devices, const std::string &device_to_configure, const std::set<std::string> &additional_devices_to_configure, const DeviceDisplayModeMap &original_modes) {
DeviceDisplayModeMap new_modes { original_modes };

if (resolution) {
// For duplicate devices the resolution must match no matter what, otherwise
// they cannot be duplicated, which breaks Windows' rules. Therefore
// we change resolution for all devices.
const auto devices { joinConfigurableDevices(device_to_configure, additional_devices_to_configure) };
for (const auto &device_id : devices) {
new_modes[device_id].m_resolution = *resolution;
}
}

if (refresh_rate) {
if (configuring_primary_devices) {
// No device has been specified, so if they're all are primary devices
// we need to apply the refresh rate change to all duplicates.
const auto devices { joinConfigurableDevices(device_to_configure, additional_devices_to_configure) };
for (const auto &device_id : devices) {
new_modes[device_id].m_refresh_rate = Rational::fromFloatingPoint(*refresh_rate);
}
}
else {
// Even if we have duplicate devices, their refresh rate may differ
// and since the device was specified, let's apply the refresh
// rate only to the specified device.
new_modes[device_to_configure].m_refresh_rate = Rational::fromFloatingPoint(*refresh_rate);
}
}

return new_modes;
}

DdGuardFn
topologyGuardFn(WinDisplayDeviceInterface &win_dd, const ActiveTopology &topology) {
DD_LOG(debug) << "Got topology in topologyGuardFn:\n"
Expand All @@ -278,7 +310,11 @@ namespace display_device::win_utils {

DdGuardFn
modeGuardFn(WinDisplayDeviceInterface &win_dd, const ActiveTopology &topology) {
const auto modes = win_dd.getCurrentDisplayModes(flattenTopology(topology));
return modeGuardFn(win_dd, win_dd.getCurrentDisplayModes(flattenTopology(topology)));
}

DdGuardFn
modeGuardFn(WinDisplayDeviceInterface &win_dd, const DeviceDisplayModeMap &modes) {
DD_LOG(debug) << "Got modes in modeGuardFn:\n"
<< toJson(modes);
return [&win_dd, modes]() {
Expand Down
13 changes: 13 additions & 0 deletions src/windows/types.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,20 @@
// header include
#include "displaydevice/windows/types.h"

// system includes
#include <cmath>

namespace display_device {
Rational
Rational::fromFloatingPoint(const double value) {
// It's hard to deal with floating values, so we just multiply it
// to keep 4 decimal places (if any) and let Windows deal with it!
// Genius idea if I'm being honest.
constexpr auto multiplier { static_cast<unsigned int>(std::pow(10, 4)) };
const double transformed_value { std::round(value * multiplier) };
return Rational { static_cast<unsigned int>(transformed_value), multiplier };
}

bool
operator==(const Rational &lhs, const Rational &rhs) {
return lhs.m_numerator == rhs.m_numerator && lhs.m_denominator == rhs.m_denominator;
Expand Down
6 changes: 3 additions & 3 deletions src/windows/winapilayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -575,7 +575,7 @@ namespace display_device {
return true;
}

std::optional<float>
std::optional<double>
WinApiLayer::getDisplayScale(const std::string &display_name, const DISPLAYCONFIG_SOURCE_MODE &source_mode) const {
// Note: implementation based on https://stackoverflow.com/a/74046173
struct EnumData {
Expand Down Expand Up @@ -616,7 +616,7 @@ namespace display_device {
return std::nullopt;
}

const auto width { static_cast<float>(*enum_data.m_width) / static_cast<float>(source_mode.width) };
return static_cast<float>(GetDpiForSystem()) / 96.0f / width;
const auto width { static_cast<double>(*enum_data.m_width) / static_cast<double>(source_mode.width) };
return static_cast<double>(GetDpiForSystem()) / 96. / width;
}
} // namespace display_device
6 changes: 3 additions & 3 deletions src/windows/winapiutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -483,9 +483,9 @@ namespace display_device::win_utils {
bool
fuzzyCompareRefreshRates(const Rational &lhs, const Rational &rhs) {
if (lhs.m_denominator > 0 && rhs.m_denominator > 0) {
const float lhs_f { static_cast<float>(lhs.m_numerator) / static_cast<float>(lhs.m_denominator) };
const float rhs_f { static_cast<float>(rhs.m_numerator) / static_cast<float>(rhs.m_denominator) };
return (std::abs(lhs_f - rhs_f) <= 0.9f);
const double lhs_f { static_cast<double>(lhs.m_numerator) / static_cast<double>(lhs.m_denominator) };
const double rhs_f { static_cast<double>(rhs.m_numerator) / static_cast<double>(rhs.m_denominator) };
return (std::abs(lhs_f - rhs_f) <= 0.9);
}

return false;
Expand Down
8 changes: 4 additions & 4 deletions src/windows/windisplaydevicegeneral.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,12 @@ namespace display_device {
}

const auto display_name { m_w_api->getDisplayName(best_path) };
const float refresh_rate { best_path.targetInfo.refreshRate.Denominator > 0 ?
static_cast<float>(best_path.targetInfo.refreshRate.Numerator) / static_cast<float>(best_path.targetInfo.refreshRate.Denominator) :
0.f };
const double refresh_rate { best_path.targetInfo.refreshRate.Denominator > 0 ?
static_cast<double>(best_path.targetInfo.refreshRate.Numerator) / static_cast<double>(best_path.targetInfo.refreshRate.Denominator) :
0. };
const EnumeratedDevice::Info info {
{ source_mode->width, source_mode->height },
m_w_api->getDisplayScale(display_name, *source_mode).value_or(0.f),
m_w_api->getDisplayScale(display_name, *source_mode).value_or(0.),
refresh_rate,
win_utils::isPrimary(*source_mode),
{ source_mode->position.x, source_mode->position.y },
Expand Down
Loading

0 comments on commit 239f0cb

Please sign in to comment.