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
5 changes: 5 additions & 0 deletions src/windows/include/displaydevice/windows/types.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -86,4 +86,9 @@ namespace display_device {
Rational m_refresh_rate;
};

/**
* @brief Ordered map of [DEVICE_ID -> DisplayMode].
*/
using DeviceDisplayModeMap = std::map<std::string, DisplayMode>;

} // namespace display_device
16 changes: 12 additions & 4 deletions src/windows/include/displaydevice/windows/windisplaydevice.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,22 +19,30 @@ namespace display_device {
*/
explicit WinDisplayDevice(std::shared_ptr<WinApiLayerInterface> w_api);

/** For details @see WinDisplayDevice::getCurrentTopology */
/** For details @see WinDisplayDeviceInterface::getCurrentTopology */
[[nodiscard]] ActiveTopology
getCurrentTopology() const override;

/** For details @see WinDisplayDevice::isTopologyValid */
/** For details @see WinDisplayDeviceInterface::isTopologyValid */
[[nodiscard]] bool
isTopologyValid(const ActiveTopology &topology) const override;

/** For details @see WinDisplayDevice::getCurrentTopology */
/** For details @see WinDisplayDeviceInterface::getCurrentTopology */
[[nodiscard]] bool
isTopologyTheSame(const ActiveTopology &lhs, const ActiveTopology &rhs) const override;

/** For details @see WinDisplayDevice::setTopology */
/** For details @see WinDisplayDeviceInterface::setTopology */
[[nodiscard]] bool
setTopology(const ActiveTopology &new_topology) override;

/** For details @see WinDisplayDeviceInterface::getCurrentDisplayModes */
[[nodiscard]] DeviceDisplayModeMap
getCurrentDisplayModes(const std::set<std::string> &device_ids) const override;

/** For details @see WinDisplayDeviceInterface::setDisplayModes */
[[nodiscard]] bool
setDisplayModes(const DeviceDisplayModeMap &modes) override;

private:
std::shared_ptr<WinApiLayerInterface> m_w_api;
};
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
#pragma once

// system includes
#include <set>

// local includes
#include "displaydevice/windows/types.h"

Expand DownExpand Up@@ -73,12 +76,48 @@ namespace display_device {
*
* EXAMPLES:
* ```cpp
* auto current_topology { getCurrentTopology() };
* const WinDisplayDeviceInterface* iface = getIface(...);
* auto current_topology { iface->getCurrentTopology() };
* // Modify the current_topology
* const bool success = setTopology(current_topology);
* const bool success = iface->setTopology(current_topology);
* ```
*/
[[nodiscard]] virtual bool
setTopology(const ActiveTopology &new_topology) = 0;

/**
* @brief Get current display modes for the devices.
* @param device_ids A list of devices to get the modes for.
* @returns A map of device modes per a device or an empty map if a mode could not be found (e.g. device is inactive).
* Empty map can also be returned if an error has occurred.
*
* EXAMPLES:
* ```cpp
* const WinDisplayDeviceInterface* iface = getIface(...);
* const std::set<std::string> device_ids { "DEVICE_ID_1", "DEVICE_ID_2" };
* const auto current_modes = iface->getCurrentDisplayModes(device_ids);
* ```
*/
[[nodiscard]] virtual DeviceDisplayModeMap
getCurrentDisplayModes(const std::set<std::string> &device_ids) const = 0;

/**
* @brief Set new display modes for the devices.
* @param modes A map of modes to set.
* @returns True if modes were set, false otherwise.
* @warning if any of the specified devices are duplicated, modes modes be provided
* for duplicates too!
*
* EXAMPLES:
* ```cpp
* const WinDisplayDeviceInterface* iface = getIface(...);
* const std::string display_a { "MY_ID_1" };
* const std::string display_b { "MY_ID_2" };
* const auto success = iface->setDisplayModes({ { display_a, { { 1920, 1080 }, { 60, 1 } } },
* { display_b, { { 1920, 1080 }, { 120, 1 } } } });
* ```
*/
[[nodiscard]] virtual bool
setDisplayModes(const DeviceDisplayModeMap &modes) = 0;
};
} // namespace display_device
236 changes: 236 additions & 0 deletions src/windows/windisplaydevicemodes.cpp
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
// class header include
#include "displaydevice/windows/windisplaydevice.h"

// system includes
#include <ranges>

// local includes
#include "displaydevice/logging.h"
#include "displaydevice/windows/winapiutils.h"

namespace display_device {
namespace {

/**
* @brief Strategy to be used when changing display modes.
*/
enum class Strategy {
Relaxed,
Strict
};

/**
* @see set_display_modes for a description as this was split off to reduce cognitive complexity.
*/
bool
doSetModes(WinApiLayerInterface &w_api, const DeviceDisplayModeMap &modes, const Strategy strategy) {
auto display_data { w_api.queryDisplayConfig(QueryType::Active) };
if (!display_data) {
// Error already logged
return false;
}

bool changes_applied { false };
for (const auto &[device_id, mode] : modes) {
const auto path { win_utils::getActivePath(w_api, device_id, display_data->m_paths) };
if (!path) {
DD_LOG(error) << "Failed to find device for " << device_id << "!";
return false;
}

const auto source_mode { win_utils::getSourceMode(win_utils::getSourceIndex(*path, display_data->m_modes), display_data->m_modes) };
if (!source_mode) {
DD_LOG(error) << "Active device does not have a source mode: " << device_id << "!";
return false;
}

bool new_changes { false };
const bool resolution_changed { source_mode->width != mode.m_resolution.m_width || source_mode->height != mode.m_resolution.m_height };

bool refresh_rate_changed;
if (strategy == Strategy::Relaxed) {
refresh_rate_changed = !win_utils::fuzzyCompareRefreshRates(Rational { path->targetInfo.refreshRate.Numerator, path->targetInfo.refreshRate.Denominator }, mode.m_refresh_rate);
}
else {
// Since we are in strict mode, do not fuzzy compare it
refresh_rate_changed = path->targetInfo.refreshRate.Numerator != mode.m_refresh_rate.m_numerator ||
path->targetInfo.refreshRate.Denominator != mode.m_refresh_rate.m_denominator;
}

if (resolution_changed) {
source_mode->width = mode.m_resolution.m_width;
source_mode->height = mode.m_resolution.m_height;
new_changes = true;
}

if (refresh_rate_changed) {
path->targetInfo.refreshRate = { mode.m_refresh_rate.m_numerator, mode.m_refresh_rate.m_denominator };
new_changes = true;
}

if (new_changes) {
// Clear the target index so that Windows has to select/modify the target to best match the requirements.
win_utils::setTargetIndex(*path, std::nullopt);
win_utils::setDesktopIndex(*path, std::nullopt); // Part of struct containing target index and so it needs to be cleared
}

changes_applied = changes_applied || new_changes;
}

if (!changes_applied) {
DD_LOG(debug) << "No changes were made to display modes as they are equal.";
return true;
}

UINT32 flags { SDC_APPLY | SDC_USE_SUPPLIED_DISPLAY_CONFIG | SDC_SAVE_TO_DATABASE | SDC_VIRTUAL_MODE_AWARE };
if (strategy == Strategy::Relaxed) {
// It's probably best for Windows to select the "best" display settings for us. However, in case we
// have custom resolution set in nvidia control panel for example, this flag will prevent successfully applying
// settings to it.
flags |= SDC_ALLOW_CHANGES;
}

const LONG result { w_api.setDisplayConfig(display_data->m_paths, display_data->m_modes, flags) };
if (result != ERROR_SUCCESS) {
DD_LOG(error) << w_api.getErrorString(result) << " failed to set display mode!";
return false;
}

return true;
}
} // namespace

DeviceDisplayModeMap
WinDisplayDevice::getCurrentDisplayModes(const std::set<std::string> &device_ids) const {
if (device_ids.empty()) {
DD_LOG(error) << "Device id set is empty!";
return {};
}

const auto display_data { m_w_api->queryDisplayConfig(QueryType::Active) };
if (!display_data) {
// Error already logged
return {};
}

DeviceDisplayModeMap current_modes;
for (const auto &device_id : device_ids) {
if (device_id.empty()) {
DD_LOG(error) << "Device id is empty!";
return {};
}

const auto path { win_utils::getActivePath(*m_w_api, device_id, display_data->m_paths) };
if (!path) {
DD_LOG(error) << "Failed to find device for " << device_id << "!";
return {};
}

const auto source_mode { win_utils::getSourceMode(win_utils::getSourceIndex(*path, display_data->m_modes), display_data->m_modes) };
if (!source_mode) {
DD_LOG(error) << "Active device does not have a source mode: " << device_id << "!";
return {};
}

// For whatever reason they put refresh rate into path, but not the resolution.
const auto target_refresh_rate { path->targetInfo.refreshRate };
current_modes[device_id] = DisplayMode {
{ source_mode->width, source_mode->height },
{ target_refresh_rate.Numerator, target_refresh_rate.Denominator }
};
}

return current_modes;
}

bool
WinDisplayDevice::setDisplayModes(const DeviceDisplayModeMap &modes) {
if (modes.empty()) {
DD_LOG(error) << "Modes map is empty!";
return false;
}

// Here it is important to check that we have all the necessary modes, otherwise
// setting modes will fail with ambiguous message.
//
// Duplicated devices can have different target modes (monitor) with different refresh rate,
// however this does not apply to the source mode (frame buffer?) and they must have same
// resolution.
//
// Without SDC_VIRTUAL_MODE_AWARE, devices would share the same source mode entry, but now
// they have separate entries that are more or less identical.
//
// To avoid surprising end-user with unexpected source mode change, we validate that all duplicate
// devices were provided instead of guessing modes automatically. This also resolve the problem of
// having to choose refresh rate for duplicate display - leave it to the end-user of this function...
const auto keys_view { std::ranges::views::keys(modes) };
const std::set<std::string> device_ids { std::begin(keys_view), std::end(keys_view) };
const auto all_device_ids { win_utils::getAllDeviceIdsAndMatchingDuplicates(*m_w_api, device_ids) };
if (all_device_ids.empty()) {
DD_LOG(error) << "Failed to get all duplicated devices!";
return false;
}

if (all_device_ids.size() != device_ids.size()) {
DD_LOG(error) << "Not all modes for duplicate displays were provided!";
return false;
}

const auto &original_data { m_w_api->queryDisplayConfig(QueryType::All) };
if (!original_data) {
// Error already logged
return false;
}

if (!doSetModes(*m_w_api, modes, Strategy::Relaxed)) {
// Error already logged
return false;
}

const auto all_modes_match = [&modes](const DeviceDisplayModeMap &current_modes) {
for (const auto &[device_id, requested_mode] : modes) {
auto mode_it { current_modes.find(device_id) };
if (mode_it == std::end(current_modes)) {
// This is a sanity check as `getCurrentDisplayModes` implicitly verifies this already.
return false;
}

if (!win_utils::fuzzyCompareModes(mode_it->second, requested_mode)) {
return false;
}
}

return true;
};

auto current_modes { getCurrentDisplayModes(device_ids) };
if (!current_modes.empty()) {
if (all_modes_match(current_modes)) {
return true;
}

// We have a problem when using SetDisplayConfig with SDC_ALLOW_CHANGES
// where it decides to use our new mode merely as a suggestion.
//
// This is good, since we don't have to be very precise with refresh rate,
// but also bad since it can just ignore our specified mode.
//
// However, it is possible that the user has created a custom display mode
// which is not exposed to the via Windows settings app. To allow this
// resolution to be selected, we actually need to omit SDC_ALLOW_CHANGES
// flag.
DD_LOG(info) << "Failed to change display modes using Windows recommended modes, trying to set modes more strictly!";
if (doSetModes(*m_w_api, modes, Strategy::Strict)) {
current_modes = getCurrentDisplayModes(device_ids);
if (!current_modes.empty() && all_modes_match(current_modes)) {
return true;
}
}
}

const UINT32 flags { SDC_APPLY | SDC_USE_SUPPLIED_DISPLAY_CONFIG | SDC_SAVE_TO_DATABASE | SDC_VIRTUAL_MODE_AWARE };
static_cast<void>(m_w_api->setDisplayConfig(original_data->m_paths, original_data->m_modes, flags)); // Return value does not matter as we are trying out best to undo
DD_LOG(error) << "Failed to set display mode(-s) completely!";
return false;
}
} // namespace display_device
23 changes: 12 additions & 11 deletions src/windows/windisplaydevicetopology.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,20 +15,14 @@ namespace display_device {
* @see set_topology for a description as this was split off to reduce cognitive complexity.
*/
bool
doSetTopology(WinApiLayerInterface &w_api, const ActiveTopology &new_topology) {
auto display_data { w_api.queryDisplayConfig(QueryType::All) };
if (!display_data) {
// Error already logged
return false;
}

const auto path_data { win_utils::collectSourceDataForMatchingPaths(w_api, display_data->m_paths) };
doSetTopology(WinApiLayerInterface &w_api, const ActiveTopology &new_topology, const PathAndModeData &display_data) {
const auto path_data { win_utils::collectSourceDataForMatchingPaths(w_api, display_data.m_paths) };
if (path_data.empty()) {
// Error already logged
return false;
}

auto paths { win_utils::makePathsForNewTopology(new_topology, path_data, display_data->m_paths) };
auto paths { win_utils::makePathsForNewTopology(new_topology, path_data, display_data.m_paths) };
if (paths.empty()) {
// Error already logged
return false;
Expand DownExpand Up@@ -161,7 +155,13 @@ namespace display_device {
return true;
}

if (doSetTopology(*m_w_api, new_topology)) {
const auto &original_data { m_w_api->queryDisplayConfig(QueryType::All) };
if (!original_data) {
// Error already logged
return false;
}

if (doSetTopology(*m_w_api, new_topology, *original_data)) {
const auto updated_topology { getCurrentTopology() };
if (isTopologyValid(updated_topology)) {
if (isTopologyTheSame(new_topology, updated_topology)) {
Expand DownExpand Up@@ -200,7 +200,8 @@ namespace display_device {
}

// Revert back to the original topology
doSetTopology(*m_w_api, current_topology); // Return value does not matter
const UINT32 flags { SDC_APPLY | SDC_USE_SUPPLIED_DISPLAY_CONFIG | SDC_SAVE_TO_DATABASE | SDC_VIRTUAL_MODE_AWARE };
static_cast<void>(m_w_api->setDisplayConfig(original_data->m_paths, original_data->m_modes, flags)); // Return value does not matter as we are trying out best to undo
}

return false;
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
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
5 changes: 5 additions & 0 deletions src/windows/include/displaydevice/windows/types.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -86,4 +86,9 @@ namespace display_device {
Rational m_refresh_rate;
};

/**
* @brief Ordered map of [DEVICE_ID -> DisplayMode].
*/
using DeviceDisplayModeMap = std::map<std::string, DisplayMode>;

} // namespace display_device
16 changes: 12 additions & 4 deletions src/windows/include/displaydevice/windows/windisplaydevice.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,22 +19,30 @@ namespace display_device {
*/
explicit WinDisplayDevice(std::shared_ptr<WinApiLayerInterface> w_api);

/** For details @see WinDisplayDevice::getCurrentTopology */
/** For details @see WinDisplayDeviceInterface::getCurrentTopology */
[[nodiscard]] ActiveTopology
getCurrentTopology() const override;

/** For details @see WinDisplayDevice::isTopologyValid */
/** For details @see WinDisplayDeviceInterface::isTopologyValid */
[[nodiscard]] bool
isTopologyValid(const ActiveTopology &topology) const override;

/** For details @see WinDisplayDevice::getCurrentTopology */
/** For details @see WinDisplayDeviceInterface::getCurrentTopology */
[[nodiscard]] bool
isTopologyTheSame(const ActiveTopology &lhs, const ActiveTopology &rhs) const override;

/** For details @see WinDisplayDevice::setTopology */
/** For details @see WinDisplayDeviceInterface::setTopology */
[[nodiscard]] bool
setTopology(const ActiveTopology &new_topology) override;

/** For details @see WinDisplayDeviceInterface::getCurrentDisplayModes */
[[nodiscard]] DeviceDisplayModeMap
getCurrentDisplayModes(const std::set<std::string> &device_ids) const override;

/** For details @see WinDisplayDeviceInterface::setDisplayModes */
[[nodiscard]] bool
setDisplayModes(const DeviceDisplayModeMap &modes) override;

private:
std::shared_ptr<WinApiLayerInterface> m_w_api;
};
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
#pragma once

// system includes
#include <set>

// local includes
#include "displaydevice/windows/types.h"

Expand DownExpand Up@@ -73,12 +76,48 @@ namespace display_device {
*
* EXAMPLES:
* ```cpp
* auto current_topology { getCurrentTopology() };
* const WinDisplayDeviceInterface* iface = getIface(...);
* auto current_topology { iface->getCurrentTopology() };
* // Modify the current_topology
* const bool success = setTopology(current_topology);
* const bool success = iface->setTopology(current_topology);
* ```
*/
[[nodiscard]] virtual bool
setTopology(const ActiveTopology &new_topology) = 0;

/**
* @brief Get current display modes for the devices.
* @param device_ids A list of devices to get the modes for.
* @returns A map of device modes per a device or an empty map if a mode could not be found (e.g. device is inactive).
* Empty map can also be returned if an error has occurred.
*
* EXAMPLES:
* ```cpp
* const WinDisplayDeviceInterface* iface = getIface(...);
* const std::set<std::string> device_ids { "DEVICE_ID_1", "DEVICE_ID_2" };
* const auto current_modes = iface->getCurrentDisplayModes(device_ids);
* ```
*/
[[nodiscard]] virtual DeviceDisplayModeMap
getCurrentDisplayModes(const std::set<std::string> &device_ids) const = 0;

/**
* @brief Set new display modes for the devices.
* @param modes A map of modes to set.
* @returns True if modes were set, false otherwise.
* @warning if any of the specified devices are duplicated, modes modes be provided
* for duplicates too!
*
* EXAMPLES:
* ```cpp
* const WinDisplayDeviceInterface* iface = getIface(...);
* const std::string display_a { "MY_ID_1" };
* const std::string display_b { "MY_ID_2" };
* const auto success = iface->setDisplayModes({ { display_a, { { 1920, 1080 }, { 60, 1 } } },
* { display_b, { { 1920, 1080 }, { 120, 1 } } } });
* ```
*/
[[nodiscard]] virtual bool
setDisplayModes(const DeviceDisplayModeMap &modes) = 0;
};
} // namespace display_device
236 changes: 236 additions & 0 deletions src/windows/windisplaydevicemodes.cpp
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
// class header include
#include "displaydevice/windows/windisplaydevice.h"

// system includes
#include <ranges>

// local includes
#include "displaydevice/logging.h"
#include "displaydevice/windows/winapiutils.h"

namespace display_device {
namespace {

/**
* @brief Strategy to be used when changing display modes.
*/
enum class Strategy {
Relaxed,
Strict
};

/**
* @see set_display_modes for a description as this was split off to reduce cognitive complexity.
*/
bool
doSetModes(WinApiLayerInterface &w_api, const DeviceDisplayModeMap &modes, const Strategy strategy) {
auto display_data { w_api.queryDisplayConfig(QueryType::Active) };
if (!display_data) {
// Error already logged
return false;
}

bool changes_applied { false };
for (const auto &[device_id, mode] : modes) {
const auto path { win_utils::getActivePath(w_api, device_id, display_data->m_paths) };
if (!path) {
DD_LOG(error) << "Failed to find device for " << device_id << "!";
return false;
}

const auto source_mode { win_utils::getSourceMode(win_utils::getSourceIndex(*path, display_data->m_modes), display_data->m_modes) };
if (!source_mode) {
DD_LOG(error) << "Active device does not have a source mode: " << device_id << "!";
return false;
}

bool new_changes { false };
const bool resolution_changed { source_mode->width != mode.m_resolution.m_width || source_mode->height != mode.m_resolution.m_height };

bool refresh_rate_changed;
if (strategy == Strategy::Relaxed) {
refresh_rate_changed = !win_utils::fuzzyCompareRefreshRates(Rational { path->targetInfo.refreshRate.Numerator, path->targetInfo.refreshRate.Denominator }, mode.m_refresh_rate);
}
else {
// Since we are in strict mode, do not fuzzy compare it
refresh_rate_changed = path->targetInfo.refreshRate.Numerator != mode.m_refresh_rate.m_numerator ||
path->targetInfo.refreshRate.Denominator != mode.m_refresh_rate.m_denominator;
}

if (resolution_changed) {
source_mode->width = mode.m_resolution.m_width;
source_mode->height = mode.m_resolution.m_height;
new_changes = true;
}

if (refresh_rate_changed) {
path->targetInfo.refreshRate = { mode.m_refresh_rate.m_numerator, mode.m_refresh_rate.m_denominator };
new_changes = true;
}

if (new_changes) {
// Clear the target index so that Windows has to select/modify the target to best match the requirements.
win_utils::setTargetIndex(*path, std::nullopt);
win_utils::setDesktopIndex(*path, std::nullopt); // Part of struct containing target index and so it needs to be cleared
}

changes_applied = changes_applied || new_changes;
}

if (!changes_applied) {
DD_LOG(debug) << "No changes were made to display modes as they are equal.";
return true;
}

UINT32 flags { SDC_APPLY | SDC_USE_SUPPLIED_DISPLAY_CONFIG | SDC_SAVE_TO_DATABASE | SDC_VIRTUAL_MODE_AWARE };
if (strategy == Strategy::Relaxed) {
// It's probably best for Windows to select the "best" display settings for us. However, in case we
// have custom resolution set in nvidia control panel for example, this flag will prevent successfully applying
// settings to it.
flags |= SDC_ALLOW_CHANGES;
}

const LONG result { w_api.setDisplayConfig(display_data->m_paths, display_data->m_modes, flags) };
if (result != ERROR_SUCCESS) {
DD_LOG(error) << w_api.getErrorString(result) << " failed to set display mode!";
return false;
}

return true;
}
} // namespace

DeviceDisplayModeMap
WinDisplayDevice::getCurrentDisplayModes(const std::set<std::string> &device_ids) const {
if (device_ids.empty()) {
DD_LOG(error) << "Device id set is empty!";
return {};
}

const auto display_data { m_w_api->queryDisplayConfig(QueryType::Active) };
if (!display_data) {
// Error already logged
return {};
}

DeviceDisplayModeMap current_modes;
for (const auto &device_id : device_ids) {
if (device_id.empty()) {
DD_LOG(error) << "Device id is empty!";
return {};
}

const auto path { win_utils::getActivePath(*m_w_api, device_id, display_data->m_paths) };
if (!path) {
DD_LOG(error) << "Failed to find device for " << device_id << "!";
return {};
}

const auto source_mode { win_utils::getSourceMode(win_utils::getSourceIndex(*path, display_data->m_modes), display_data->m_modes) };
if (!source_mode) {
DD_LOG(error) << "Active device does not have a source mode: " << device_id << "!";
return {};
}

// For whatever reason they put refresh rate into path, but not the resolution.
const auto target_refresh_rate { path->targetInfo.refreshRate };
current_modes[device_id] = DisplayMode {
{ source_mode->width, source_mode->height },
{ target_refresh_rate.Numerator, target_refresh_rate.Denominator }
};
}

return current_modes;
}

bool
WinDisplayDevice::setDisplayModes(const DeviceDisplayModeMap &modes) {
if (modes.empty()) {
DD_LOG(error) << "Modes map is empty!";
return false;
}

// Here it is important to check that we have all the necessary modes, otherwise
// setting modes will fail with ambiguous message.
//
// Duplicated devices can have different target modes (monitor) with different refresh rate,
// however this does not apply to the source mode (frame buffer?) and they must have same
// resolution.
//
// Without SDC_VIRTUAL_MODE_AWARE, devices would share the same source mode entry, but now
// they have separate entries that are more or less identical.
//
// To avoid surprising end-user with unexpected source mode change, we validate that all duplicate
// devices were provided instead of guessing modes automatically. This also resolve the problem of
// having to choose refresh rate for duplicate display - leave it to the end-user of this function...
const auto keys_view { std::ranges::views::keys(modes) };
const std::set<std::string> device_ids { std::begin(keys_view), std::end(keys_view) };
const auto all_device_ids { win_utils::getAllDeviceIdsAndMatchingDuplicates(*m_w_api, device_ids) };
if (all_device_ids.empty()) {
DD_LOG(error) << "Failed to get all duplicated devices!";
return false;
}

if (all_device_ids.size() != device_ids.size()) {
DD_LOG(error) << "Not all modes for duplicate displays were provided!";
return false;
}

const auto &original_data { m_w_api->queryDisplayConfig(QueryType::All) };
if (!original_data) {
// Error already logged
return false;
}

if (!doSetModes(*m_w_api, modes, Strategy::Relaxed)) {
// Error already logged
return false;
}

const auto all_modes_match = [&modes](const DeviceDisplayModeMap &current_modes) {
for (const auto &[device_id, requested_mode] : modes) {
auto mode_it { current_modes.find(device_id) };
if (mode_it == std::end(current_modes)) {
// This is a sanity check as `getCurrentDisplayModes` implicitly verifies this already.
return false;
}

if (!win_utils::fuzzyCompareModes(mode_it->second, requested_mode)) {
return false;
}
}

return true;
};

auto current_modes { getCurrentDisplayModes(device_ids) };
if (!current_modes.empty()) {
if (all_modes_match(current_modes)) {
return true;
}

// We have a problem when using SetDisplayConfig with SDC_ALLOW_CHANGES
// where it decides to use our new mode merely as a suggestion.
//
// This is good, since we don't have to be very precise with refresh rate,
// but also bad since it can just ignore our specified mode.
//
// However, it is possible that the user has created a custom display mode
// which is not exposed to the via Windows settings app. To allow this
// resolution to be selected, we actually need to omit SDC_ALLOW_CHANGES
// flag.
DD_LOG(info) << "Failed to change display modes using Windows recommended modes, trying to set modes more strictly!";
if (doSetModes(*m_w_api, modes, Strategy::Strict)) {
current_modes = getCurrentDisplayModes(device_ids);
if (!current_modes.empty() && all_modes_match(current_modes)) {
return true;
}
}
}

const UINT32 flags { SDC_APPLY | SDC_USE_SUPPLIED_DISPLAY_CONFIG | SDC_SAVE_TO_DATABASE | SDC_VIRTUAL_MODE_AWARE };
static_cast<void>(m_w_api->setDisplayConfig(original_data->m_paths, original_data->m_modes, flags)); // Return value does not matter as we are trying out best to undo
DD_LOG(error) << "Failed to set display mode(-s) completely!";
return false;
}
} // namespace display_device
23 changes: 12 additions & 11 deletions src/windows/windisplaydevicetopology.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,20 +15,14 @@ namespace display_device {
* @see set_topology for a description as this was split off to reduce cognitive complexity.
*/
bool
doSetTopology(WinApiLayerInterface &w_api, const ActiveTopology &new_topology) {
auto display_data { w_api.queryDisplayConfig(QueryType::All) };
if (!display_data) {
// Error already logged
return false;
}

const auto path_data { win_utils::collectSourceDataForMatchingPaths(w_api, display_data->m_paths) };
doSetTopology(WinApiLayerInterface &w_api, const ActiveTopology &new_topology, const PathAndModeData &display_data) {
const auto path_data { win_utils::collectSourceDataForMatchingPaths(w_api, display_data.m_paths) };
if (path_data.empty()) {
// Error already logged
return false;
}

auto paths { win_utils::makePathsForNewTopology(new_topology, path_data, display_data->m_paths) };
auto paths { win_utils::makePathsForNewTopology(new_topology, path_data, display_data.m_paths) };
if (paths.empty()) {
// Error already logged
return false;
Expand DownExpand Up@@ -161,7 +155,13 @@ namespace display_device {
return true;
}

if (doSetTopology(*m_w_api, new_topology)) {
const auto &original_data { m_w_api->queryDisplayConfig(QueryType::All) };
if (!original_data) {
// Error already logged
return false;
}

if (doSetTopology(*m_w_api, new_topology, *original_data)) {
const auto updated_topology { getCurrentTopology() };
if (isTopologyValid(updated_topology)) {
if (isTopologyTheSame(new_topology, updated_topology)) {
Expand DownExpand Up@@ -200,7 +200,8 @@ namespace display_device {
}

// Revert back to the original topology
doSetTopology(*m_w_api, current_topology); // Return value does not matter
const UINT32 flags { SDC_APPLY | SDC_USE_SUPPLIED_DISPLAY_CONFIG | SDC_SAVE_TO_DATABASE | SDC_VIRTUAL_MODE_AWARE };
static_cast<void>(m_w_api->setDisplayConfig(original_data->m_paths, original_data->m_modes, flags)); // Return value does not matter as we are trying out best to undo
}

return false;
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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
5 changes: 5 additions & 0 deletions src/windows/include/displaydevice/windows/types.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -86,4 +86,9 @@ namespace display_device {
Rational m_refresh_rate;
};

/**
* @brief Ordered map of [DEVICE_ID -> DisplayMode].
*/
using DeviceDisplayModeMap = std::map<std::string, DisplayMode>;

} // namespace display_device
16 changes: 12 additions & 4 deletions src/windows/include/displaydevice/windows/windisplaydevice.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,22 +19,30 @@ namespace display_device {
*/
explicit WinDisplayDevice(std::shared_ptr<WinApiLayerInterface> w_api);

/** For details @see WinDisplayDevice::getCurrentTopology */
/** For details @see WinDisplayDeviceInterface::getCurrentTopology */
[[nodiscard]] ActiveTopology
getCurrentTopology() const override;

/** For details @see WinDisplayDevice::isTopologyValid */
/** For details @see WinDisplayDeviceInterface::isTopologyValid */
[[nodiscard]] bool
isTopologyValid(const ActiveTopology &topology) const override;

/** For details @see WinDisplayDevice::getCurrentTopology */
/** For details @see WinDisplayDeviceInterface::getCurrentTopology */
[[nodiscard]] bool
isTopologyTheSame(const ActiveTopology &lhs, const ActiveTopology &rhs) const override;

/** For details @see WinDisplayDevice::setTopology */
/** For details @see WinDisplayDeviceInterface::setTopology */
[[nodiscard]] bool
setTopology(const ActiveTopology &new_topology) override;

/** For details @see WinDisplayDeviceInterface::getCurrentDisplayModes */
[[nodiscard]] DeviceDisplayModeMap
getCurrentDisplayModes(const std::set<std::string> &device_ids) const override;

/** For details @see WinDisplayDeviceInterface::setDisplayModes */
[[nodiscard]] bool
setDisplayModes(const DeviceDisplayModeMap &modes) override;

private:
std::shared_ptr<WinApiLayerInterface> m_w_api;
};
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
#pragma once

// system includes
#include <set>

// local includes
#include "displaydevice/windows/types.h"

Expand DownExpand Up@@ -73,12 +76,48 @@ namespace display_device {
*
* EXAMPLES:
* ```cpp
* auto current_topology { getCurrentTopology() };
* const WinDisplayDeviceInterface* iface = getIface(...);
* auto current_topology { iface->getCurrentTopology() };
* // Modify the current_topology
* const bool success = setTopology(current_topology);
* const bool success = iface->setTopology(current_topology);
* ```
*/
[[nodiscard]] virtual bool
setTopology(const ActiveTopology &new_topology) = 0;

/**
* @brief Get current display modes for the devices.
* @param device_ids A list of devices to get the modes for.
* @returns A map of device modes per a device or an empty map if a mode could not be found (e.g. device is inactive).
* Empty map can also be returned if an error has occurred.
*
* EXAMPLES:
* ```cpp
* const WinDisplayDeviceInterface* iface = getIface(...);
* const std::set<std::string> device_ids { "DEVICE_ID_1", "DEVICE_ID_2" };
* const auto current_modes = iface->getCurrentDisplayModes(device_ids);
* ```
*/
[[nodiscard]] virtual DeviceDisplayModeMap
getCurrentDisplayModes(const std::set<std::string> &device_ids) const = 0;

/**
* @brief Set new display modes for the devices.
* @param modes A map of modes to set.
* @returns True if modes were set, false otherwise.
* @warning if any of the specified devices are duplicated, modes modes be provided
* for duplicates too!
*
* EXAMPLES:
* ```cpp
* const WinDisplayDeviceInterface* iface = getIface(...);
* const std::string display_a { "MY_ID_1" };
* const std::string display_b { "MY_ID_2" };
* const auto success = iface->setDisplayModes({ { display_a, { { 1920, 1080 }, { 60, 1 } } },
* { display_b, { { 1920, 1080 }, { 120, 1 } } } });
* ```
*/
[[nodiscard]] virtual bool
setDisplayModes(const DeviceDisplayModeMap &modes) = 0;
};
} // namespace display_device
236 changes: 236 additions & 0 deletions src/windows/windisplaydevicemodes.cpp
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
// class header include
#include "displaydevice/windows/windisplaydevice.h"

// system includes
#include <ranges>

// local includes
#include "displaydevice/logging.h"
#include "displaydevice/windows/winapiutils.h"

namespace display_device {
namespace {

/**
* @brief Strategy to be used when changing display modes.
*/
enum class Strategy {
Relaxed,
Strict
};

/**
* @see set_display_modes for a description as this was split off to reduce cognitive complexity.
*/
bool
doSetModes(WinApiLayerInterface &w_api, const DeviceDisplayModeMap &modes, const Strategy strategy) {
auto display_data { w_api.queryDisplayConfig(QueryType::Active) };
if (!display_data) {
// Error already logged
return false;
}

bool changes_applied { false };
for (const auto &[device_id, mode] : modes) {
const auto path { win_utils::getActivePath(w_api, device_id, display_data->m_paths) };
if (!path) {
DD_LOG(error) << "Failed to find device for " << device_id << "!";
return false;
}

const auto source_mode { win_utils::getSourceMode(win_utils::getSourceIndex(*path, display_data->m_modes), display_data->m_modes) };
if (!source_mode) {
DD_LOG(error) << "Active device does not have a source mode: " << device_id << "!";
return false;
}

bool new_changes { false };
const bool resolution_changed { source_mode->width != mode.m_resolution.m_width || source_mode->height != mode.m_resolution.m_height };

bool refresh_rate_changed;
if (strategy == Strategy::Relaxed) {
refresh_rate_changed = !win_utils::fuzzyCompareRefreshRates(Rational { path->targetInfo.refreshRate.Numerator, path->targetInfo.refreshRate.Denominator }, mode.m_refresh_rate);
}
else {
// Since we are in strict mode, do not fuzzy compare it
refresh_rate_changed = path->targetInfo.refreshRate.Numerator != mode.m_refresh_rate.m_numerator ||
path->targetInfo.refreshRate.Denominator != mode.m_refresh_rate.m_denominator;
}

if (resolution_changed) {
source_mode->width = mode.m_resolution.m_width;
source_mode->height = mode.m_resolution.m_height;
new_changes = true;
}

if (refresh_rate_changed) {
path->targetInfo.refreshRate = { mode.m_refresh_rate.m_numerator, mode.m_refresh_rate.m_denominator };
new_changes = true;
}

if (new_changes) {
// Clear the target index so that Windows has to select/modify the target to best match the requirements.
win_utils::setTargetIndex(*path, std::nullopt);
win_utils::setDesktopIndex(*path, std::nullopt); // Part of struct containing target index and so it needs to be cleared
}

changes_applied = changes_applied || new_changes;
}

if (!changes_applied) {
DD_LOG(debug) << "No changes were made to display modes as they are equal.";
return true;
}

UINT32 flags { SDC_APPLY | SDC_USE_SUPPLIED_DISPLAY_CONFIG | SDC_SAVE_TO_DATABASE | SDC_VIRTUAL_MODE_AWARE };
if (strategy == Strategy::Relaxed) {
// It's probably best for Windows to select the "best" display settings for us. However, in case we
// have custom resolution set in nvidia control panel for example, this flag will prevent successfully applying
// settings to it.
flags |= SDC_ALLOW_CHANGES;
}

const LONG result { w_api.setDisplayConfig(display_data->m_paths, display_data->m_modes, flags) };
if (result != ERROR_SUCCESS) {
DD_LOG(error) << w_api.getErrorString(result) << " failed to set display mode!";
return false;
}

return true;
}
} // namespace

DeviceDisplayModeMap
WinDisplayDevice::getCurrentDisplayModes(const std::set<std::string> &device_ids) const {
if (device_ids.empty()) {
DD_LOG(error) << "Device id set is empty!";
return {};
}

const auto display_data { m_w_api->queryDisplayConfig(QueryType::Active) };
if (!display_data) {
// Error already logged
return {};
}

DeviceDisplayModeMap current_modes;
for (const auto &device_id : device_ids) {
if (device_id.empty()) {
DD_LOG(error) << "Device id is empty!";
return {};
}

const auto path { win_utils::getActivePath(*m_w_api, device_id, display_data->m_paths) };
if (!path) {
DD_LOG(error) << "Failed to find device for " << device_id << "!";
return {};
}

const auto source_mode { win_utils::getSourceMode(win_utils::getSourceIndex(*path, display_data->m_modes), display_data->m_modes) };
if (!source_mode) {
DD_LOG(error) << "Active device does not have a source mode: " << device_id << "!";
return {};
}

// For whatever reason they put refresh rate into path, but not the resolution.
const auto target_refresh_rate { path->targetInfo.refreshRate };
current_modes[device_id] = DisplayMode {
{ source_mode->width, source_mode->height },
{ target_refresh_rate.Numerator, target_refresh_rate.Denominator }
};
}

return current_modes;
}

bool
WinDisplayDevice::setDisplayModes(const DeviceDisplayModeMap &modes) {
if (modes.empty()) {
DD_LOG(error) << "Modes map is empty!";
return false;
}

// Here it is important to check that we have all the necessary modes, otherwise
// setting modes will fail with ambiguous message.
//
// Duplicated devices can have different target modes (monitor) with different refresh rate,
// however this does not apply to the source mode (frame buffer?) and they must have same
// resolution.
//
// Without SDC_VIRTUAL_MODE_AWARE, devices would share the same source mode entry, but now
// they have separate entries that are more or less identical.
//
// To avoid surprising end-user with unexpected source mode change, we validate that all duplicate
// devices were provided instead of guessing modes automatically. This also resolve the problem of
// having to choose refresh rate for duplicate display - leave it to the end-user of this function...
const auto keys_view { std::ranges::views::keys(modes) };
const std::set<std::string> device_ids { std::begin(keys_view), std::end(keys_view) };
const auto all_device_ids { win_utils::getAllDeviceIdsAndMatchingDuplicates(*m_w_api, device_ids) };
if (all_device_ids.empty()) {
DD_LOG(error) << "Failed to get all duplicated devices!";
return false;
}

if (all_device_ids.size() != device_ids.size()) {
DD_LOG(error) << "Not all modes for duplicate displays were provided!";
return false;
}

const auto &original_data { m_w_api->queryDisplayConfig(QueryType::All) };
if (!original_data) {
// Error already logged
return false;
}

if (!doSetModes(*m_w_api, modes, Strategy::Relaxed)) {
// Error already logged
return false;
}

const auto all_modes_match = [&modes](const DeviceDisplayModeMap &current_modes) {
for (const auto &[device_id, requested_mode] : modes) {
auto mode_it { current_modes.find(device_id) };
if (mode_it == std::end(current_modes)) {
// This is a sanity check as `getCurrentDisplayModes` implicitly verifies this already.
return false;
}

if (!win_utils::fuzzyCompareModes(mode_it->second, requested_mode)) {
return false;
}
}

return true;
};

auto current_modes { getCurrentDisplayModes(device_ids) };
if (!current_modes.empty()) {
if (all_modes_match(current_modes)) {
return true;
}

// We have a problem when using SetDisplayConfig with SDC_ALLOW_CHANGES
// where it decides to use our new mode merely as a suggestion.
//
// This is good, since we don't have to be very precise with refresh rate,
// but also bad since it can just ignore our specified mode.
//
// However, it is possible that the user has created a custom display mode
// which is not exposed to the via Windows settings app. To allow this
// resolution to be selected, we actually need to omit SDC_ALLOW_CHANGES
// flag.
DD_LOG(info) << "Failed to change display modes using Windows recommended modes, trying to set modes more strictly!";
if (doSetModes(*m_w_api, modes, Strategy::Strict)) {
current_modes = getCurrentDisplayModes(device_ids);
if (!current_modes.empty() && all_modes_match(current_modes)) {
return true;
}
}
}

const UINT32 flags { SDC_APPLY | SDC_USE_SUPPLIED_DISPLAY_CONFIG | SDC_SAVE_TO_DATABASE | SDC_VIRTUAL_MODE_AWARE };
static_cast<void>(m_w_api->setDisplayConfig(original_data->m_paths, original_data->m_modes, flags)); // Return value does not matter as we are trying out best to undo
DD_LOG(error) << "Failed to set display mode(-s) completely!";
return false;
}
} // namespace display_device
23 changes: 12 additions & 11 deletions src/windows/windisplaydevicetopology.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,20 +15,14 @@ namespace display_device {
* @see set_topology for a description as this was split off to reduce cognitive complexity.
*/
bool
doSetTopology(WinApiLayerInterface &w_api, const ActiveTopology &new_topology) {
auto display_data { w_api.queryDisplayConfig(QueryType::All) };
if (!display_data) {
// Error already logged
return false;
}

const auto path_data { win_utils::collectSourceDataForMatchingPaths(w_api, display_data->m_paths) };
doSetTopology(WinApiLayerInterface &w_api, const ActiveTopology &new_topology, const PathAndModeData &display_data) {
const auto path_data { win_utils::collectSourceDataForMatchingPaths(w_api, display_data.m_paths) };
if (path_data.empty()) {
// Error already logged
return false;
}

auto paths { win_utils::makePathsForNewTopology(new_topology, path_data, display_data->m_paths) };
auto paths { win_utils::makePathsForNewTopology(new_topology, path_data, display_data.m_paths) };
if (paths.empty()) {
// Error already logged
return false;
Expand DownExpand Up@@ -161,7 +155,13 @@ namespace display_device {
return true;
}

if (doSetTopology(*m_w_api, new_topology)) {
const auto &original_data { m_w_api->queryDisplayConfig(QueryType::All) };
if (!original_data) {
// Error already logged
return false;
}

if (doSetTopology(*m_w_api, new_topology, *original_data)) {
const auto updated_topology { getCurrentTopology() };
if (isTopologyValid(updated_topology)) {
if (isTopologyTheSame(new_topology, updated_topology)) {
Expand DownExpand Up@@ -200,7 +200,8 @@ namespace display_device {
}

// Revert back to the original topology
doSetTopology(*m_w_api, current_topology); // Return value does not matter
const UINT32 flags { SDC_APPLY | SDC_USE_SUPPLIED_DISPLAY_CONFIG | SDC_SAVE_TO_DATABASE | SDC_VIRTUAL_MODE_AWARE };
static_cast<void>(m_w_api->setDisplayConfig(original_data->m_paths, original_data->m_modes, flags)); // Return value does not matter as we are trying out best to undo
}

return false;
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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
5 changes: 5 additions & 0 deletions src/windows/include/displaydevice/windows/types.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -86,4 +86,9 @@ namespace display_device {
Rational m_refresh_rate;
};

/**
* @brief Ordered map of [DEVICE_ID -> DisplayMode].
*/
using DeviceDisplayModeMap = std::map<std::string, DisplayMode>;

} // namespace display_device
16 changes: 12 additions & 4 deletions src/windows/include/displaydevice/windows/windisplaydevice.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,22 +19,30 @@ namespace display_device {
*/
explicit WinDisplayDevice(std::shared_ptr<WinApiLayerInterface> w_api);

/** For details @see WinDisplayDevice::getCurrentTopology */
/** For details @see WinDisplayDeviceInterface::getCurrentTopology */
[[nodiscard]] ActiveTopology
getCurrentTopology() const override;

/** For details @see WinDisplayDevice::isTopologyValid */
/** For details @see WinDisplayDeviceInterface::isTopologyValid */
[[nodiscard]] bool
isTopologyValid(const ActiveTopology &topology) const override;

/** For details @see WinDisplayDevice::getCurrentTopology */
/** For details @see WinDisplayDeviceInterface::getCurrentTopology */
[[nodiscard]] bool
isTopologyTheSame(const ActiveTopology &lhs, const ActiveTopology &rhs) const override;

/** For details @see WinDisplayDevice::setTopology */
/** For details @see WinDisplayDeviceInterface::setTopology */
[[nodiscard]] bool
setTopology(const ActiveTopology &new_topology) override;

/** For details @see WinDisplayDeviceInterface::getCurrentDisplayModes */
[[nodiscard]] DeviceDisplayModeMap
getCurrentDisplayModes(const std::set<std::string> &device_ids) const override;

/** For details @see WinDisplayDeviceInterface::setDisplayModes */
[[nodiscard]] bool
setDisplayModes(const DeviceDisplayModeMap &modes) override;

private:
std::shared_ptr<WinApiLayerInterface> m_w_api;
};
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
#pragma once

// system includes
#include <set>

// local includes
#include "displaydevice/windows/types.h"

Expand DownExpand Up@@ -73,12 +76,48 @@ namespace display_device {
*
* EXAMPLES:
* ```cpp
* auto current_topology { getCurrentTopology() };
* const WinDisplayDeviceInterface* iface = getIface(...);
* auto current_topology { iface->getCurrentTopology() };
* // Modify the current_topology
* const bool success = setTopology(current_topology);
* const bool success = iface->setTopology(current_topology);
* ```
*/
[[nodiscard]] virtual bool
setTopology(const ActiveTopology &new_topology) = 0;

/**
* @brief Get current display modes for the devices.
* @param device_ids A list of devices to get the modes for.
* @returns A map of device modes per a device or an empty map if a mode could not be found (e.g. device is inactive).
* Empty map can also be returned if an error has occurred.
*
* EXAMPLES:
* ```cpp
* const WinDisplayDeviceInterface* iface = getIface(...);
* const std::set<std::string> device_ids { "DEVICE_ID_1", "DEVICE_ID_2" };
* const auto current_modes = iface->getCurrentDisplayModes(device_ids);
* ```
*/
[[nodiscard]] virtual DeviceDisplayModeMap
getCurrentDisplayModes(const std::set<std::string> &device_ids) const = 0;

/**
* @brief Set new display modes for the devices.
* @param modes A map of modes to set.
* @returns True if modes were set, false otherwise.
* @warning if any of the specified devices are duplicated, modes modes be provided
* for duplicates too!
*
* EXAMPLES:
* ```cpp
* const WinDisplayDeviceInterface* iface = getIface(...);
* const std::string display_a { "MY_ID_1" };
* const std::string display_b { "MY_ID_2" };
* const auto success = iface->setDisplayModes({ { display_a, { { 1920, 1080 }, { 60, 1 } } },
* { display_b, { { 1920, 1080 }, { 120, 1 } } } });
* ```
*/
[[nodiscard]] virtual bool
setDisplayModes(const DeviceDisplayModeMap &modes) = 0;
};
} // namespace display_device
236 changes: 236 additions & 0 deletions src/windows/windisplaydevicemodes.cpp
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
// class header include
#include "displaydevice/windows/windisplaydevice.h"

// system includes
#include <ranges>

// local includes
#include "displaydevice/logging.h"
#include "displaydevice/windows/winapiutils.h"

namespace display_device {
namespace {

/**
* @brief Strategy to be used when changing display modes.
*/
enum class Strategy {
Relaxed,
Strict
};

/**
* @see set_display_modes for a description as this was split off to reduce cognitive complexity.
*/
bool
doSetModes(WinApiLayerInterface &w_api, const DeviceDisplayModeMap &modes, const Strategy strategy) {
auto display_data { w_api.queryDisplayConfig(QueryType::Active) };
if (!display_data) {
// Error already logged
return false;
}

bool changes_applied { false };
for (const auto &[device_id, mode] : modes) {
const auto path { win_utils::getActivePath(w_api, device_id, display_data->m_paths) };
if (!path) {
DD_LOG(error) << "Failed to find device for " << device_id << "!";
return false;
}

const auto source_mode { win_utils::getSourceMode(win_utils::getSourceIndex(*path, display_data->m_modes), display_data->m_modes) };
if (!source_mode) {
DD_LOG(error) << "Active device does not have a source mode: " << device_id << "!";
return false;
}

bool new_changes { false };
const bool resolution_changed { source_mode->width != mode.m_resolution.m_width || source_mode->height != mode.m_resolution.m_height };

bool refresh_rate_changed;
if (strategy == Strategy::Relaxed) {
refresh_rate_changed = !win_utils::fuzzyCompareRefreshRates(Rational { path->targetInfo.refreshRate.Numerator, path->targetInfo.refreshRate.Denominator }, mode.m_refresh_rate);
}
else {
// Since we are in strict mode, do not fuzzy compare it
refresh_rate_changed = path->targetInfo.refreshRate.Numerator != mode.m_refresh_rate.m_numerator ||
path->targetInfo.refreshRate.Denominator != mode.m_refresh_rate.m_denominator;
}

if (resolution_changed) {
source_mode->width = mode.m_resolution.m_width;
source_mode->height = mode.m_resolution.m_height;
new_changes = true;
}

if (refresh_rate_changed) {
path->targetInfo.refreshRate = { mode.m_refresh_rate.m_numerator, mode.m_refresh_rate.m_denominator };
new_changes = true;
}

if (new_changes) {
// Clear the target index so that Windows has to select/modify the target to best match the requirements.
win_utils::setTargetIndex(*path, std::nullopt);
win_utils::setDesktopIndex(*path, std::nullopt); // Part of struct containing target index and so it needs to be cleared
}

changes_applied = changes_applied || new_changes;
}

if (!changes_applied) {
DD_LOG(debug) << "No changes were made to display modes as they are equal.";
return true;
}

UINT32 flags { SDC_APPLY | SDC_USE_SUPPLIED_DISPLAY_CONFIG | SDC_SAVE_TO_DATABASE | SDC_VIRTUAL_MODE_AWARE };
if (strategy == Strategy::Relaxed) {
// It's probably best for Windows to select the "best" display settings for us. However, in case we
// have custom resolution set in nvidia control panel for example, this flag will prevent successfully applying
// settings to it.
flags |= SDC_ALLOW_CHANGES;
}

const LONG result { w_api.setDisplayConfig(display_data->m_paths, display_data->m_modes, flags) };
if (result != ERROR_SUCCESS) {
DD_LOG(error) << w_api.getErrorString(result) << " failed to set display mode!";
return false;
}

return true;
}
} // namespace

DeviceDisplayModeMap
WinDisplayDevice::getCurrentDisplayModes(const std::set<std::string> &device_ids) const {
if (device_ids.empty()) {
DD_LOG(error) << "Device id set is empty!";
return {};
}

const auto display_data { m_w_api->queryDisplayConfig(QueryType::Active) };
if (!display_data) {
// Error already logged
return {};
}

DeviceDisplayModeMap current_modes;
for (const auto &device_id : device_ids) {
if (device_id.empty()) {
DD_LOG(error) << "Device id is empty!";
return {};
}

const auto path { win_utils::getActivePath(*m_w_api, device_id, display_data->m_paths) };
if (!path) {
DD_LOG(error) << "Failed to find device for " << device_id << "!";
return {};
}

const auto source_mode { win_utils::getSourceMode(win_utils::getSourceIndex(*path, display_data->m_modes), display_data->m_modes) };
if (!source_mode) {
DD_LOG(error) << "Active device does not have a source mode: " << device_id << "!";
return {};
}

// For whatever reason they put refresh rate into path, but not the resolution.
const auto target_refresh_rate { path->targetInfo.refreshRate };
current_modes[device_id] = DisplayMode {
{ source_mode->width, source_mode->height },
{ target_refresh_rate.Numerator, target_refresh_rate.Denominator }
};
}

return current_modes;
}

bool
WinDisplayDevice::setDisplayModes(const DeviceDisplayModeMap &modes) {
if (modes.empty()) {
DD_LOG(error) << "Modes map is empty!";
return false;
}

// Here it is important to check that we have all the necessary modes, otherwise
// setting modes will fail with ambiguous message.
//
// Duplicated devices can have different target modes (monitor) with different refresh rate,
// however this does not apply to the source mode (frame buffer?) and they must have same
// resolution.
//
// Without SDC_VIRTUAL_MODE_AWARE, devices would share the same source mode entry, but now
// they have separate entries that are more or less identical.
//
// To avoid surprising end-user with unexpected source mode change, we validate that all duplicate
// devices were provided instead of guessing modes automatically. This also resolve the problem of
// having to choose refresh rate for duplicate display - leave it to the end-user of this function...
const auto keys_view { std::ranges::views::keys(modes) };
const std::set<std::string> device_ids { std::begin(keys_view), std::end(keys_view) };
const auto all_device_ids { win_utils::getAllDeviceIdsAndMatchingDuplicates(*m_w_api, device_ids) };
if (all_device_ids.empty()) {
DD_LOG(error) << "Failed to get all duplicated devices!";
return false;
}

if (all_device_ids.size() != device_ids.size()) {
DD_LOG(error) << "Not all modes for duplicate displays were provided!";
return false;
}

const auto &original_data { m_w_api->queryDisplayConfig(QueryType::All) };
if (!original_data) {
// Error already logged
return false;
}

if (!doSetModes(*m_w_api, modes, Strategy::Relaxed)) {
// Error already logged
return false;
}

const auto all_modes_match = [&modes](const DeviceDisplayModeMap &current_modes) {
for (const auto &[device_id, requested_mode] : modes) {
auto mode_it { current_modes.find(device_id) };
if (mode_it == std::end(current_modes)) {
// This is a sanity check as `getCurrentDisplayModes` implicitly verifies this already.
return false;
}

if (!win_utils::fuzzyCompareModes(mode_it->second, requested_mode)) {
return false;
}
}

return true;
};

auto current_modes { getCurrentDisplayModes(device_ids) };
if (!current_modes.empty()) {
if (all_modes_match(current_modes)) {
return true;
}

// We have a problem when using SetDisplayConfig with SDC_ALLOW_CHANGES
// where it decides to use our new mode merely as a suggestion.
//
// This is good, since we don't have to be very precise with refresh rate,
// but also bad since it can just ignore our specified mode.
//
// However, it is possible that the user has created a custom display mode
// which is not exposed to the via Windows settings app. To allow this
// resolution to be selected, we actually need to omit SDC_ALLOW_CHANGES
// flag.
DD_LOG(info) << "Failed to change display modes using Windows recommended modes, trying to set modes more strictly!";
if (doSetModes(*m_w_api, modes, Strategy::Strict)) {
current_modes = getCurrentDisplayModes(device_ids);
if (!current_modes.empty() && all_modes_match(current_modes)) {
return true;
}
}
}

const UINT32 flags { SDC_APPLY | SDC_USE_SUPPLIED_DISPLAY_CONFIG | SDC_SAVE_TO_DATABASE | SDC_VIRTUAL_MODE_AWARE };
static_cast<void>(m_w_api->setDisplayConfig(original_data->m_paths, original_data->m_modes, flags)); // Return value does not matter as we are trying out best to undo
DD_LOG(error) << "Failed to set display mode(-s) completely!";
return false;
}
} // namespace display_device
23 changes: 12 additions & 11 deletions src/windows/windisplaydevicetopology.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,20 +15,14 @@ namespace display_device {
* @see set_topology for a description as this was split off to reduce cognitive complexity.
*/
bool
doSetTopology(WinApiLayerInterface &w_api, const ActiveTopology &new_topology) {
auto display_data { w_api.queryDisplayConfig(QueryType::All) };
if (!display_data) {
// Error already logged
return false;
}

const auto path_data { win_utils::collectSourceDataForMatchingPaths(w_api, display_data->m_paths) };
doSetTopology(WinApiLayerInterface &w_api, const ActiveTopology &new_topology, const PathAndModeData &display_data) {
const auto path_data { win_utils::collectSourceDataForMatchingPaths(w_api, display_data.m_paths) };
if (path_data.empty()) {
// Error already logged
return false;
}

auto paths { win_utils::makePathsForNewTopology(new_topology, path_data, display_data->m_paths) };
auto paths { win_utils::makePathsForNewTopology(new_topology, path_data, display_data.m_paths) };
if (paths.empty()) {
// Error already logged
return false;
Expand DownExpand Up@@ -161,7 +155,13 @@ namespace display_device {
return true;
}

if (doSetTopology(*m_w_api, new_topology)) {
const auto &original_data { m_w_api->queryDisplayConfig(QueryType::All) };
if (!original_data) {
// Error already logged
return false;
}

if (doSetTopology(*m_w_api, new_topology, *original_data)) {
const auto updated_topology { getCurrentTopology() };
if (isTopologyValid(updated_topology)) {
if (isTopologyTheSame(new_topology, updated_topology)) {
Expand DownExpand Up@@ -200,7 +200,8 @@ namespace display_device {
}

// Revert back to the original topology
doSetTopology(*m_w_api, current_topology); // Return value does not matter
const UINT32 flags { SDC_APPLY | SDC_USE_SUPPLIED_DISPLAY_CONFIG | SDC_SAVE_TO_DATABASE | SDC_VIRTUAL_MODE_AWARE };
static_cast<void>(m_w_api->setDisplayConfig(original_data->m_paths, original_data->m_modes, flags)); // Return value does not matter as we are trying out best to undo
}

return false;
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
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
5 changes: 5 additions & 0 deletions src/windows/include/displaydevice/windows/types.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -86,4 +86,9 @@ namespace display_device {
Rational m_refresh_rate;
};

/**
* @brief Ordered map of [DEVICE_ID -> DisplayMode].
*/
using DeviceDisplayModeMap = std::map<std::string, DisplayMode>;

} // namespace display_device
16 changes: 12 additions & 4 deletions src/windows/include/displaydevice/windows/windisplaydevice.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,22 +19,30 @@ namespace display_device {
*/
explicit WinDisplayDevice(std::shared_ptr<WinApiLayerInterface> w_api);

/** For details @see WinDisplayDevice::getCurrentTopology */
/** For details @see WinDisplayDeviceInterface::getCurrentTopology */
[[nodiscard]] ActiveTopology
getCurrentTopology() const override;

/** For details @see WinDisplayDevice::isTopologyValid */
/** For details @see WinDisplayDeviceInterface::isTopologyValid */
[[nodiscard]] bool
isTopologyValid(const ActiveTopology &topology) const override;

/** For details @see WinDisplayDevice::getCurrentTopology */
/** For details @see WinDisplayDeviceInterface::getCurrentTopology */
[[nodiscard]] bool
isTopologyTheSame(const ActiveTopology &lhs, const ActiveTopology &rhs) const override;

/** For details @see WinDisplayDevice::setTopology */
/** For details @see WinDisplayDeviceInterface::setTopology */
[[nodiscard]] bool
setTopology(const ActiveTopology &new_topology) override;

/** For details @see WinDisplayDeviceInterface::getCurrentDisplayModes */
[[nodiscard]] DeviceDisplayModeMap
getCurrentDisplayModes(const std::set<std::string> &device_ids) const override;

/** For details @see WinDisplayDeviceInterface::setDisplayModes */
[[nodiscard]] bool
setDisplayModes(const DeviceDisplayModeMap &modes) override;

private:
std::shared_ptr<WinApiLayerInterface> m_w_api;
};
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
#pragma once

// system includes
#include <set>

// local includes
#include "displaydevice/windows/types.h"

Expand DownExpand Up@@ -73,12 +76,48 @@ namespace display_device {
*
* EXAMPLES:
* ```cpp
* auto current_topology { getCurrentTopology() };
* const WinDisplayDeviceInterface* iface = getIface(...);
* auto current_topology { iface->getCurrentTopology() };
* // Modify the current_topology
* const bool success = setTopology(current_topology);
* const bool success = iface->setTopology(current_topology);
* ```
*/
[[nodiscard]] virtual bool
setTopology(const ActiveTopology &new_topology) = 0;

/**
* @brief Get current display modes for the devices.
* @param device_ids A list of devices to get the modes for.
* @returns A map of device modes per a device or an empty map if a mode could not be found (e.g. device is inactive).
* Empty map can also be returned if an error has occurred.
*
* EXAMPLES:
* ```cpp
* const WinDisplayDeviceInterface* iface = getIface(...);
* const std::set<std::string> device_ids { "DEVICE_ID_1", "DEVICE_ID_2" };
* const auto current_modes = iface->getCurrentDisplayModes(device_ids);
* ```
*/
[[nodiscard]] virtual DeviceDisplayModeMap
getCurrentDisplayModes(const std::set<std::string> &device_ids) const = 0;

/**
* @brief Set new display modes for the devices.
* @param modes A map of modes to set.
* @returns True if modes were set, false otherwise.
* @warning if any of the specified devices are duplicated, modes modes be provided
* for duplicates too!
*
* EXAMPLES:
* ```cpp
* const WinDisplayDeviceInterface* iface = getIface(...);
* const std::string display_a { "MY_ID_1" };
* const std::string display_b { "MY_ID_2" };
* const auto success = iface->setDisplayModes({ { display_a, { { 1920, 1080 }, { 60, 1 } } },
* { display_b, { { 1920, 1080 }, { 120, 1 } } } });
* ```
*/
[[nodiscard]] virtual bool
setDisplayModes(const DeviceDisplayModeMap &modes) = 0;
};
} // namespace display_device
236 changes: 236 additions & 0 deletions src/windows/windisplaydevicemodes.cpp
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
// class header include
#include "displaydevice/windows/windisplaydevice.h"

// system includes
#include <ranges>

// local includes
#include "displaydevice/logging.h"
#include "displaydevice/windows/winapiutils.h"

namespace display_device {
namespace {

/**
* @brief Strategy to be used when changing display modes.
*/
enum class Strategy {
Relaxed,
Strict
};

/**
* @see set_display_modes for a description as this was split off to reduce cognitive complexity.
*/
bool
doSetModes(WinApiLayerInterface &w_api, const DeviceDisplayModeMap &modes, const Strategy strategy) {
auto display_data { w_api.queryDisplayConfig(QueryType::Active) };
if (!display_data) {
// Error already logged
return false;
}

bool changes_applied { false };
for (const auto &[device_id, mode] : modes) {
const auto path { win_utils::getActivePath(w_api, device_id, display_data->m_paths) };
if (!path) {
DD_LOG(error) << "Failed to find device for " << device_id << "!";
return false;
}

const auto source_mode { win_utils::getSourceMode(win_utils::getSourceIndex(*path, display_data->m_modes), display_data->m_modes) };
if (!source_mode) {
DD_LOG(error) << "Active device does not have a source mode: " << device_id << "!";
return false;
}

bool new_changes { false };
const bool resolution_changed { source_mode->width != mode.m_resolution.m_width || source_mode->height != mode.m_resolution.m_height };

bool refresh_rate_changed;
if (strategy == Strategy::Relaxed) {
refresh_rate_changed = !win_utils::fuzzyCompareRefreshRates(Rational { path->targetInfo.refreshRate.Numerator, path->targetInfo.refreshRate.Denominator }, mode.m_refresh_rate);
}
else {
// Since we are in strict mode, do not fuzzy compare it
refresh_rate_changed = path->targetInfo.refreshRate.Numerator != mode.m_refresh_rate.m_numerator ||
path->targetInfo.refreshRate.Denominator != mode.m_refresh_rate.m_denominator;
}

if (resolution_changed) {
source_mode->width = mode.m_resolution.m_width;
source_mode->height = mode.m_resolution.m_height;
new_changes = true;
}

if (refresh_rate_changed) {
path->targetInfo.refreshRate = { mode.m_refresh_rate.m_numerator, mode.m_refresh_rate.m_denominator };
new_changes = true;
}

if (new_changes) {
// Clear the target index so that Windows has to select/modify the target to best match the requirements.
win_utils::setTargetIndex(*path, std::nullopt);
win_utils::setDesktopIndex(*path, std::nullopt); // Part of struct containing target index and so it needs to be cleared
}

changes_applied = changes_applied || new_changes;
}

if (!changes_applied) {
DD_LOG(debug) << "No changes were made to display modes as they are equal.";
return true;
}

UINT32 flags { SDC_APPLY | SDC_USE_SUPPLIED_DISPLAY_CONFIG | SDC_SAVE_TO_DATABASE | SDC_VIRTUAL_MODE_AWARE };
if (strategy == Strategy::Relaxed) {
// It's probably best for Windows to select the "best" display settings for us. However, in case we
// have custom resolution set in nvidia control panel for example, this flag will prevent successfully applying
// settings to it.
flags |= SDC_ALLOW_CHANGES;
}

const LONG result { w_api.setDisplayConfig(display_data->m_paths, display_data->m_modes, flags) };
if (result != ERROR_SUCCESS) {
DD_LOG(error) << w_api.getErrorString(result) << " failed to set display mode!";
return false;
}

return true;
}
} // namespace

DeviceDisplayModeMap
WinDisplayDevice::getCurrentDisplayModes(const std::set<std::string> &device_ids) const {
if (device_ids.empty()) {
DD_LOG(error) << "Device id set is empty!";
return {};
}

const auto display_data { m_w_api->queryDisplayConfig(QueryType::Active) };
if (!display_data) {
// Error already logged
return {};
}

DeviceDisplayModeMap current_modes;
for (const auto &device_id : device_ids) {
if (device_id.empty()) {
DD_LOG(error) << "Device id is empty!";
return {};
}

const auto path { win_utils::getActivePath(*m_w_api, device_id, display_data->m_paths) };
if (!path) {
DD_LOG(error) << "Failed to find device for " << device_id << "!";
return {};
}

const auto source_mode { win_utils::getSourceMode(win_utils::getSourceIndex(*path, display_data->m_modes), display_data->m_modes) };
if (!source_mode) {
DD_LOG(error) << "Active device does not have a source mode: " << device_id << "!";
return {};
}

// For whatever reason they put refresh rate into path, but not the resolution.
const auto target_refresh_rate { path->targetInfo.refreshRate };
current_modes[device_id] = DisplayMode {
{ source_mode->width, source_mode->height },
{ target_refresh_rate.Numerator, target_refresh_rate.Denominator }
};
}

return current_modes;
}

bool
WinDisplayDevice::setDisplayModes(const DeviceDisplayModeMap &modes) {
if (modes.empty()) {
DD_LOG(error) << "Modes map is empty!";
return false;
}

// Here it is important to check that we have all the necessary modes, otherwise
// setting modes will fail with ambiguous message.
//
// Duplicated devices can have different target modes (monitor) with different refresh rate,
// however this does not apply to the source mode (frame buffer?) and they must have same
// resolution.
//
// Without SDC_VIRTUAL_MODE_AWARE, devices would share the same source mode entry, but now
// they have separate entries that are more or less identical.
//
// To avoid surprising end-user with unexpected source mode change, we validate that all duplicate
// devices were provided instead of guessing modes automatically. This also resolve the problem of
// having to choose refresh rate for duplicate display - leave it to the end-user of this function...
const auto keys_view { std::ranges::views::keys(modes) };
const std::set<std::string> device_ids { std::begin(keys_view), std::end(keys_view) };
const auto all_device_ids { win_utils::getAllDeviceIdsAndMatchingDuplicates(*m_w_api, device_ids) };
if (all_device_ids.empty()) {
DD_LOG(error) << "Failed to get all duplicated devices!";
return false;
}

if (all_device_ids.size() != device_ids.size()) {
DD_LOG(error) << "Not all modes for duplicate displays were provided!";
return false;
}

const auto &original_data { m_w_api->queryDisplayConfig(QueryType::All) };
if (!original_data) {
// Error already logged
return false;
}

if (!doSetModes(*m_w_api, modes, Strategy::Relaxed)) {
// Error already logged
return false;
}

const auto all_modes_match = [&modes](const DeviceDisplayModeMap &current_modes) {
for (const auto &[device_id, requested_mode] : modes) {
auto mode_it { current_modes.find(device_id) };
if (mode_it == std::end(current_modes)) {
// This is a sanity check as `getCurrentDisplayModes` implicitly verifies this already.
return false;
}

if (!win_utils::fuzzyCompareModes(mode_it->second, requested_mode)) {
return false;
}
}

return true;
};

auto current_modes { getCurrentDisplayModes(device_ids) };
if (!current_modes.empty()) {
if (all_modes_match(current_modes)) {
return true;
}

// We have a problem when using SetDisplayConfig with SDC_ALLOW_CHANGES
// where it decides to use our new mode merely as a suggestion.
//
// This is good, since we don't have to be very precise with refresh rate,
// but also bad since it can just ignore our specified mode.
//
// However, it is possible that the user has created a custom display mode
// which is not exposed to the via Windows settings app. To allow this
// resolution to be selected, we actually need to omit SDC_ALLOW_CHANGES
// flag.
DD_LOG(info) << "Failed to change display modes using Windows recommended modes, trying to set modes more strictly!";
if (doSetModes(*m_w_api, modes, Strategy::Strict)) {
current_modes = getCurrentDisplayModes(device_ids);
if (!current_modes.empty() && all_modes_match(current_modes)) {
return true;
}
}
}

const UINT32 flags { SDC_APPLY | SDC_USE_SUPPLIED_DISPLAY_CONFIG | SDC_SAVE_TO_DATABASE | SDC_VIRTUAL_MODE_AWARE };
static_cast<void>(m_w_api->setDisplayConfig(original_data->m_paths, original_data->m_modes, flags)); // Return value does not matter as we are trying out best to undo
DD_LOG(error) << "Failed to set display mode(-s) completely!";
return false;
}
} // namespace display_device
23 changes: 12 additions & 11 deletions src/windows/windisplaydevicetopology.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,20 +15,14 @@ namespace display_device {
* @see set_topology for a description as this was split off to reduce cognitive complexity.
*/
bool
doSetTopology(WinApiLayerInterface &w_api, const ActiveTopology &new_topology) {
auto display_data { w_api.queryDisplayConfig(QueryType::All) };
if (!display_data) {
// Error already logged
return false;
}

const auto path_data { win_utils::collectSourceDataForMatchingPaths(w_api, display_data->m_paths) };
doSetTopology(WinApiLayerInterface &w_api, const ActiveTopology &new_topology, const PathAndModeData &display_data) {
const auto path_data { win_utils::collectSourceDataForMatchingPaths(w_api, display_data.m_paths) };
if (path_data.empty()) {
// Error already logged
return false;
}

auto paths { win_utils::makePathsForNewTopology(new_topology, path_data, display_data->m_paths) };
auto paths { win_utils::makePathsForNewTopology(new_topology, path_data, display_data.m_paths) };
if (paths.empty()) {
// Error already logged
return false;
Expand DownExpand Up@@ -161,7 +155,13 @@ namespace display_device {
return true;
}

if (doSetTopology(*m_w_api, new_topology)) {
const auto &original_data { m_w_api->queryDisplayConfig(QueryType::All) };
if (!original_data) {
// Error already logged
return false;
}

if (doSetTopology(*m_w_api, new_topology, *original_data)) {
const auto updated_topology { getCurrentTopology() };
if (isTopologyValid(updated_topology)) {
if (isTopologyTheSame(new_topology, updated_topology)) {
Expand DownExpand Up@@ -200,7 +200,8 @@ namespace display_device {
}

// Revert back to the original topology
doSetTopology(*m_w_api, current_topology); // Return value does not matter
const UINT32 flags { SDC_APPLY | SDC_USE_SUPPLIED_DISPLAY_CONFIG | SDC_SAVE_TO_DATABASE | SDC_VIRTUAL_MODE_AWARE };
static_cast<void>(m_w_api->setDisplayConfig(original_data->m_paths, original_data->m_modes, flags)); // Return value does not matter as we are trying out best to undo
}

return false;
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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
5 changes: 5 additions & 0 deletions src/windows/include/displaydevice/windows/types.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -86,4 +86,9 @@ namespace display_device {
Rational m_refresh_rate;
};

/**
* @brief Ordered map of [DEVICE_ID -> DisplayMode].
*/
using DeviceDisplayModeMap = std::map<std::string, DisplayMode>;

} // namespace display_device
16 changes: 12 additions & 4 deletions src/windows/include/displaydevice/windows/windisplaydevice.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,22 +19,30 @@ namespace display_device {
*/
explicit WinDisplayDevice(std::shared_ptr<WinApiLayerInterface> w_api);

/** For details @see WinDisplayDevice::getCurrentTopology */
/** For details @see WinDisplayDeviceInterface::getCurrentTopology */
[[nodiscard]] ActiveTopology
getCurrentTopology() const override;

/** For details @see WinDisplayDevice::isTopologyValid */
/** For details @see WinDisplayDeviceInterface::isTopologyValid */
[[nodiscard]] bool
isTopologyValid(const ActiveTopology &topology) const override;

/** For details @see WinDisplayDevice::getCurrentTopology */
/** For details @see WinDisplayDeviceInterface::getCurrentTopology */
[[nodiscard]] bool
isTopologyTheSame(const ActiveTopology &lhs, const ActiveTopology &rhs) const override;

/** For details @see WinDisplayDevice::setTopology */
/** For details @see WinDisplayDeviceInterface::setTopology */
[[nodiscard]] bool
setTopology(const ActiveTopology &new_topology) override;

/** For details @see WinDisplayDeviceInterface::getCurrentDisplayModes */
[[nodiscard]] DeviceDisplayModeMap
getCurrentDisplayModes(const std::set<std::string> &device_ids) const override;

/** For details @see WinDisplayDeviceInterface::setDisplayModes */
[[nodiscard]] bool
setDisplayModes(const DeviceDisplayModeMap &modes) override;

private:
std::shared_ptr<WinApiLayerInterface> m_w_api;
};
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
#pragma once

// system includes
#include <set>

// local includes
#include "displaydevice/windows/types.h"

Expand DownExpand Up@@ -73,12 +76,48 @@ namespace display_device {
*
* EXAMPLES:
* ```cpp
* auto current_topology { getCurrentTopology() };
* const WinDisplayDeviceInterface* iface = getIface(...);
* auto current_topology { iface->getCurrentTopology() };
* // Modify the current_topology
* const bool success = setTopology(current_topology);
* const bool success = iface->setTopology(current_topology);
* ```
*/
[[nodiscard]] virtual bool
setTopology(const ActiveTopology &new_topology) = 0;

/**
* @brief Get current display modes for the devices.
* @param device_ids A list of devices to get the modes for.
* @returns A map of device modes per a device or an empty map if a mode could not be found (e.g. device is inactive).
* Empty map can also be returned if an error has occurred.
*
* EXAMPLES:
* ```cpp
* const WinDisplayDeviceInterface* iface = getIface(...);
* const std::set<std::string> device_ids { "DEVICE_ID_1", "DEVICE_ID_2" };
* const auto current_modes = iface->getCurrentDisplayModes(device_ids);
* ```
*/
[[nodiscard]] virtual DeviceDisplayModeMap
getCurrentDisplayModes(const std::set<std::string> &device_ids) const = 0;

/**
* @brief Set new display modes for the devices.
* @param modes A map of modes to set.
* @returns True if modes were set, false otherwise.
* @warning if any of the specified devices are duplicated, modes modes be provided
* for duplicates too!
*
* EXAMPLES:
* ```cpp
* const WinDisplayDeviceInterface* iface = getIface(...);
* const std::string display_a { "MY_ID_1" };
* const std::string display_b { "MY_ID_2" };
* const auto success = iface->setDisplayModes({ { display_a, { { 1920, 1080 }, { 60, 1 } } },
* { display_b, { { 1920, 1080 }, { 120, 1 } } } });
* ```
*/
[[nodiscard]] virtual bool
setDisplayModes(const DeviceDisplayModeMap &modes) = 0;
};
} // namespace display_device
236 changes: 236 additions & 0 deletions src/windows/windisplaydevicemodes.cpp
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
// class header include
#include "displaydevice/windows/windisplaydevice.h"

// system includes
#include <ranges>

// local includes
#include "displaydevice/logging.h"
#include "displaydevice/windows/winapiutils.h"

namespace display_device {
namespace {

/**
* @brief Strategy to be used when changing display modes.
*/
enum class Strategy {
Relaxed,
Strict
};

/**
* @see set_display_modes for a description as this was split off to reduce cognitive complexity.
*/
bool
doSetModes(WinApiLayerInterface &w_api, const DeviceDisplayModeMap &modes, const Strategy strategy) {
auto display_data { w_api.queryDisplayConfig(QueryType::Active) };
if (!display_data) {
// Error already logged
return false;
}

bool changes_applied { false };
for (const auto &[device_id, mode] : modes) {
const auto path { win_utils::getActivePath(w_api, device_id, display_data->m_paths) };
if (!path) {
DD_LOG(error) << "Failed to find device for " << device_id << "!";
return false;
}

const auto source_mode { win_utils::getSourceMode(win_utils::getSourceIndex(*path, display_data->m_modes), display_data->m_modes) };
if (!source_mode) {
DD_LOG(error) << "Active device does not have a source mode: " << device_id << "!";
return false;
}

bool new_changes { false };
const bool resolution_changed { source_mode->width != mode.m_resolution.m_width || source_mode->height != mode.m_resolution.m_height };

bool refresh_rate_changed;
if (strategy == Strategy::Relaxed) {
refresh_rate_changed = !win_utils::fuzzyCompareRefreshRates(Rational { path->targetInfo.refreshRate.Numerator, path->targetInfo.refreshRate.Denominator }, mode.m_refresh_rate);
}
else {
// Since we are in strict mode, do not fuzzy compare it
refresh_rate_changed = path->targetInfo.refreshRate.Numerator != mode.m_refresh_rate.m_numerator ||
path->targetInfo.refreshRate.Denominator != mode.m_refresh_rate.m_denominator;
}

if (resolution_changed) {
source_mode->width = mode.m_resolution.m_width;
source_mode->height = mode.m_resolution.m_height;
new_changes = true;
}

if (refresh_rate_changed) {
path->targetInfo.refreshRate = { mode.m_refresh_rate.m_numerator, mode.m_refresh_rate.m_denominator };
new_changes = true;
}

if (new_changes) {
// Clear the target index so that Windows has to select/modify the target to best match the requirements.
win_utils::setTargetIndex(*path, std::nullopt);
win_utils::setDesktopIndex(*path, std::nullopt); // Part of struct containing target index and so it needs to be cleared
}

changes_applied = changes_applied || new_changes;
}

if (!changes_applied) {
DD_LOG(debug) << "No changes were made to display modes as they are equal.";
return true;
}

UINT32 flags { SDC_APPLY | SDC_USE_SUPPLIED_DISPLAY_CONFIG | SDC_SAVE_TO_DATABASE | SDC_VIRTUAL_MODE_AWARE };
if (strategy == Strategy::Relaxed) {
// It's probably best for Windows to select the "best" display settings for us. However, in case we
// have custom resolution set in nvidia control panel for example, this flag will prevent successfully applying
// settings to it.
flags |= SDC_ALLOW_CHANGES;
}

const LONG result { w_api.setDisplayConfig(display_data->m_paths, display_data->m_modes, flags) };
if (result != ERROR_SUCCESS) {
DD_LOG(error) << w_api.getErrorString(result) << " failed to set display mode!";
return false;
}

return true;
}
} // namespace

DeviceDisplayModeMap
WinDisplayDevice::getCurrentDisplayModes(const std::set<std::string> &device_ids) const {
if (device_ids.empty()) {
DD_LOG(error) << "Device id set is empty!";
return {};
}

const auto display_data { m_w_api->queryDisplayConfig(QueryType::Active) };
if (!display_data) {
// Error already logged
return {};
}

DeviceDisplayModeMap current_modes;
for (const auto &device_id : device_ids) {
if (device_id.empty()) {
DD_LOG(error) << "Device id is empty!";
return {};
}

const auto path { win_utils::getActivePath(*m_w_api, device_id, display_data->m_paths) };
if (!path) {
DD_LOG(error) << "Failed to find device for " << device_id << "!";
return {};
}

const auto source_mode { win_utils::getSourceMode(win_utils::getSourceIndex(*path, display_data->m_modes), display_data->m_modes) };
if (!source_mode) {
DD_LOG(error) << "Active device does not have a source mode: " << device_id << "!";
return {};
}

// For whatever reason they put refresh rate into path, but not the resolution.
const auto target_refresh_rate { path->targetInfo.refreshRate };
current_modes[device_id] = DisplayMode {
{ source_mode->width, source_mode->height },
{ target_refresh_rate.Numerator, target_refresh_rate.Denominator }
};
}

return current_modes;
}

bool
WinDisplayDevice::setDisplayModes(const DeviceDisplayModeMap &modes) {
if (modes.empty()) {
DD_LOG(error) << "Modes map is empty!";
return false;
}

// Here it is important to check that we have all the necessary modes, otherwise
// setting modes will fail with ambiguous message.
//
// Duplicated devices can have different target modes (monitor) with different refresh rate,
// however this does not apply to the source mode (frame buffer?) and they must have same
// resolution.
//
// Without SDC_VIRTUAL_MODE_AWARE, devices would share the same source mode entry, but now
// they have separate entries that are more or less identical.
//
// To avoid surprising end-user with unexpected source mode change, we validate that all duplicate
// devices were provided instead of guessing modes automatically. This also resolve the problem of
// having to choose refresh rate for duplicate display - leave it to the end-user of this function...
const auto keys_view { std::ranges::views::keys(modes) };
const std::set<std::string> device_ids { std::begin(keys_view), std::end(keys_view) };
const auto all_device_ids { win_utils::getAllDeviceIdsAndMatchingDuplicates(*m_w_api, device_ids) };
if (all_device_ids.empty()) {
DD_LOG(error) << "Failed to get all duplicated devices!";
return false;
}

if (all_device_ids.size() != device_ids.size()) {
DD_LOG(error) << "Not all modes for duplicate displays were provided!";
return false;
}

const auto &original_data { m_w_api->queryDisplayConfig(QueryType::All) };
if (!original_data) {
// Error already logged
return false;
}

if (!doSetModes(*m_w_api, modes, Strategy::Relaxed)) {
// Error already logged
return false;
}

const auto all_modes_match = [&modes](const DeviceDisplayModeMap &current_modes) {
for (const auto &[device_id, requested_mode] : modes) {
auto mode_it { current_modes.find(device_id) };
if (mode_it == std::end(current_modes)) {
// This is a sanity check as `getCurrentDisplayModes` implicitly verifies this already.
return false;
}

if (!win_utils::fuzzyCompareModes(mode_it->second, requested_mode)) {
return false;
}
}

return true;
};

auto current_modes { getCurrentDisplayModes(device_ids) };
if (!current_modes.empty()) {
if (all_modes_match(current_modes)) {
return true;
}

// We have a problem when using SetDisplayConfig with SDC_ALLOW_CHANGES
// where it decides to use our new mode merely as a suggestion.
//
// This is good, since we don't have to be very precise with refresh rate,
// but also bad since it can just ignore our specified mode.
//
// However, it is possible that the user has created a custom display mode
// which is not exposed to the via Windows settings app. To allow this
// resolution to be selected, we actually need to omit SDC_ALLOW_CHANGES
// flag.
DD_LOG(info) << "Failed to change display modes using Windows recommended modes, trying to set modes more strictly!";
if (doSetModes(*m_w_api, modes, Strategy::Strict)) {
current_modes = getCurrentDisplayModes(device_ids);
if (!current_modes.empty() && all_modes_match(current_modes)) {
return true;
}
}
}

const UINT32 flags { SDC_APPLY | SDC_USE_SUPPLIED_DISPLAY_CONFIG | SDC_SAVE_TO_DATABASE | SDC_VIRTUAL_MODE_AWARE };
static_cast<void>(m_w_api->setDisplayConfig(original_data->m_paths, original_data->m_modes, flags)); // Return value does not matter as we are trying out best to undo
DD_LOG(error) << "Failed to set display mode(-s) completely!";
return false;
}
} // namespace display_device
23 changes: 12 additions & 11 deletions src/windows/windisplaydevicetopology.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,20 +15,14 @@ namespace display_device {
* @see set_topology for a description as this was split off to reduce cognitive complexity.
*/
bool
doSetTopology(WinApiLayerInterface &w_api, const ActiveTopology &new_topology) {
auto display_data { w_api.queryDisplayConfig(QueryType::All) };
if (!display_data) {
// Error already logged
return false;
}

const auto path_data { win_utils::collectSourceDataForMatchingPaths(w_api, display_data->m_paths) };
doSetTopology(WinApiLayerInterface &w_api, const ActiveTopology &new_topology, const PathAndModeData &display_data) {
const auto path_data { win_utils::collectSourceDataForMatchingPaths(w_api, display_data.m_paths) };
if (path_data.empty()) {
// Error already logged
return false;
}

auto paths { win_utils::makePathsForNewTopology(new_topology, path_data, display_data->m_paths) };
auto paths { win_utils::makePathsForNewTopology(new_topology, path_data, display_data.m_paths) };
if (paths.empty()) {
// Error already logged
return false;
Expand DownExpand Up@@ -161,7 +155,13 @@ namespace display_device {
return true;
}

if (doSetTopology(*m_w_api, new_topology)) {
const auto &original_data { m_w_api->queryDisplayConfig(QueryType::All) };
if (!original_data) {
// Error already logged
return false;
}

if (doSetTopology(*m_w_api, new_topology, *original_data)) {
const auto updated_topology { getCurrentTopology() };
if (isTopologyValid(updated_topology)) {
if (isTopologyTheSame(new_topology, updated_topology)) {
Expand DownExpand Up@@ -200,7 +200,8 @@ namespace display_device {
}

// Revert back to the original topology
doSetTopology(*m_w_api, current_topology); // Return value does not matter
const UINT32 flags { SDC_APPLY | SDC_USE_SUPPLIED_DISPLAY_CONFIG | SDC_SAVE_TO_DATABASE | SDC_VIRTUAL_MODE_AWARE };
static_cast<void>(m_w_api->setDisplayConfig(original_data->m_paths, original_data->m_modes, flags)); // Return value does not matter as we are trying out best to undo
}

return false;
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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
5 changes: 5 additions & 0 deletions src/windows/include/displaydevice/windows/types.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -86,4 +86,9 @@ namespace display_device {
Rational m_refresh_rate;
};

/**
* @brief Ordered map of [DEVICE_ID -> DisplayMode].
*/
using DeviceDisplayModeMap = std::map<std::string, DisplayMode>;

} // namespace display_device
16 changes: 12 additions & 4 deletions src/windows/include/displaydevice/windows/windisplaydevice.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,22 +19,30 @@ namespace display_device {
*/
explicit WinDisplayDevice(std::shared_ptr<WinApiLayerInterface> w_api);

/** For details @see WinDisplayDevice::getCurrentTopology */
/** For details @see WinDisplayDeviceInterface::getCurrentTopology */
[[nodiscard]] ActiveTopology
getCurrentTopology() const override;

/** For details @see WinDisplayDevice::isTopologyValid */
/** For details @see WinDisplayDeviceInterface::isTopologyValid */
[[nodiscard]] bool
isTopologyValid(const ActiveTopology &topology) const override;

/** For details @see WinDisplayDevice::getCurrentTopology */
/** For details @see WinDisplayDeviceInterface::getCurrentTopology */
[[nodiscard]] bool
isTopologyTheSame(const ActiveTopology &lhs, const ActiveTopology &rhs) const override;

/** For details @see WinDisplayDevice::setTopology */
/** For details @see WinDisplayDeviceInterface::setTopology */
[[nodiscard]] bool
setTopology(const ActiveTopology &new_topology) override;

/** For details @see WinDisplayDeviceInterface::getCurrentDisplayModes */
[[nodiscard]] DeviceDisplayModeMap
getCurrentDisplayModes(const std::set<std::string> &device_ids) const override;

/** For details @see WinDisplayDeviceInterface::setDisplayModes */
[[nodiscard]] bool
setDisplayModes(const DeviceDisplayModeMap &modes) override;

private:
std::shared_ptr<WinApiLayerInterface> m_w_api;
};
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
#pragma once

// system includes
#include <set>

// local includes
#include "displaydevice/windows/types.h"

Expand DownExpand Up@@ -73,12 +76,48 @@ namespace display_device {
*
* EXAMPLES:
* ```cpp
* auto current_topology { getCurrentTopology() };
* const WinDisplayDeviceInterface* iface = getIface(...);
* auto current_topology { iface->getCurrentTopology() };
* // Modify the current_topology
* const bool success = setTopology(current_topology);
* const bool success = iface->setTopology(current_topology);
* ```
*/
[[nodiscard]] virtual bool
setTopology(const ActiveTopology &new_topology) = 0;

/**
* @brief Get current display modes for the devices.
* @param device_ids A list of devices to get the modes for.
* @returns A map of device modes per a device or an empty map if a mode could not be found (e.g. device is inactive).
* Empty map can also be returned if an error has occurred.
*
* EXAMPLES:
* ```cpp
* const WinDisplayDeviceInterface* iface = getIface(...);
* const std::set<std::string> device_ids { "DEVICE_ID_1", "DEVICE_ID_2" };
* const auto current_modes = iface->getCurrentDisplayModes(device_ids);
* ```
*/
[[nodiscard]] virtual DeviceDisplayModeMap
getCurrentDisplayModes(const std::set<std::string> &device_ids) const = 0;

/**
* @brief Set new display modes for the devices.
* @param modes A map of modes to set.
* @returns True if modes were set, false otherwise.
* @warning if any of the specified devices are duplicated, modes modes be provided
* for duplicates too!
*
* EXAMPLES:
* ```cpp
* const WinDisplayDeviceInterface* iface = getIface(...);
* const std::string display_a { "MY_ID_1" };
* const std::string display_b { "MY_ID_2" };
* const auto success = iface->setDisplayModes({ { display_a, { { 1920, 1080 }, { 60, 1 } } },
* { display_b, { { 1920, 1080 }, { 120, 1 } } } });
* ```
*/
[[nodiscard]] virtual bool
setDisplayModes(const DeviceDisplayModeMap &modes) = 0;
};
} // namespace display_device
236 changes: 236 additions & 0 deletions src/windows/windisplaydevicemodes.cpp
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
// class header include
#include "displaydevice/windows/windisplaydevice.h"

// system includes
#include <ranges>

// local includes
#include "displaydevice/logging.h"
#include "displaydevice/windows/winapiutils.h"

namespace display_device {
namespace {

/**
* @brief Strategy to be used when changing display modes.
*/
enum class Strategy {
Relaxed,
Strict
};

/**
* @see set_display_modes for a description as this was split off to reduce cognitive complexity.
*/
bool
doSetModes(WinApiLayerInterface &w_api, const DeviceDisplayModeMap &modes, const Strategy strategy) {
auto display_data { w_api.queryDisplayConfig(QueryType::Active) };
if (!display_data) {
// Error already logged
return false;
}

bool changes_applied { false };
for (const auto &[device_id, mode] : modes) {
const auto path { win_utils::getActivePath(w_api, device_id, display_data->m_paths) };
if (!path) {
DD_LOG(error) << "Failed to find device for " << device_id << "!";
return false;
}

const auto source_mode { win_utils::getSourceMode(win_utils::getSourceIndex(*path, display_data->m_modes), display_data->m_modes) };
if (!source_mode) {
DD_LOG(error) << "Active device does not have a source mode: " << device_id << "!";
return false;
}

bool new_changes { false };
const bool resolution_changed { source_mode->width != mode.m_resolution.m_width || source_mode->height != mode.m_resolution.m_height };

bool refresh_rate_changed;
if (strategy == Strategy::Relaxed) {
refresh_rate_changed = !win_utils::fuzzyCompareRefreshRates(Rational { path->targetInfo.refreshRate.Numerator, path->targetInfo.refreshRate.Denominator }, mode.m_refresh_rate);
}
else {
// Since we are in strict mode, do not fuzzy compare it
refresh_rate_changed = path->targetInfo.refreshRate.Numerator != mode.m_refresh_rate.m_numerator ||
path->targetInfo.refreshRate.Denominator != mode.m_refresh_rate.m_denominator;
}

if (resolution_changed) {
source_mode->width = mode.m_resolution.m_width;
source_mode->height = mode.m_resolution.m_height;
new_changes = true;
}

if (refresh_rate_changed) {
path->targetInfo.refreshRate = { mode.m_refresh_rate.m_numerator, mode.m_refresh_rate.m_denominator };
new_changes = true;
}

if (new_changes) {
// Clear the target index so that Windows has to select/modify the target to best match the requirements.
win_utils::setTargetIndex(*path, std::nullopt);
win_utils::setDesktopIndex(*path, std::nullopt); // Part of struct containing target index and so it needs to be cleared
}

changes_applied = changes_applied || new_changes;
}

if (!changes_applied) {
DD_LOG(debug) << "No changes were made to display modes as they are equal.";
return true;
}

UINT32 flags { SDC_APPLY | SDC_USE_SUPPLIED_DISPLAY_CONFIG | SDC_SAVE_TO_DATABASE | SDC_VIRTUAL_MODE_AWARE };
if (strategy == Strategy::Relaxed) {
// It's probably best for Windows to select the "best" display settings for us. However, in case we
// have custom resolution set in nvidia control panel for example, this flag will prevent successfully applying
// settings to it.
flags |= SDC_ALLOW_CHANGES;
}

const LONG result { w_api.setDisplayConfig(display_data->m_paths, display_data->m_modes, flags) };
if (result != ERROR_SUCCESS) {
DD_LOG(error) << w_api.getErrorString(result) << " failed to set display mode!";
return false;
}

return true;
}
} // namespace

DeviceDisplayModeMap
WinDisplayDevice::getCurrentDisplayModes(const std::set<std::string> &device_ids) const {
if (device_ids.empty()) {
DD_LOG(error) << "Device id set is empty!";
return {};
}

const auto display_data { m_w_api->queryDisplayConfig(QueryType::Active) };
if (!display_data) {
// Error already logged
return {};
}

DeviceDisplayModeMap current_modes;
for (const auto &device_id : device_ids) {
if (device_id.empty()) {
DD_LOG(error) << "Device id is empty!";
return {};
}

const auto path { win_utils::getActivePath(*m_w_api, device_id, display_data->m_paths) };
if (!path) {
DD_LOG(error) << "Failed to find device for " << device_id << "!";
return {};
}

const auto source_mode { win_utils::getSourceMode(win_utils::getSourceIndex(*path, display_data->m_modes), display_data->m_modes) };
if (!source_mode) {
DD_LOG(error) << "Active device does not have a source mode: " << device_id << "!";
return {};
}

// For whatever reason they put refresh rate into path, but not the resolution.
const auto target_refresh_rate { path->targetInfo.refreshRate };
current_modes[device_id] = DisplayMode {
{ source_mode->width, source_mode->height },
{ target_refresh_rate.Numerator, target_refresh_rate.Denominator }
};
}

return current_modes;
}

bool
WinDisplayDevice::setDisplayModes(const DeviceDisplayModeMap &modes) {
if (modes.empty()) {
DD_LOG(error) << "Modes map is empty!";
return false;
}

// Here it is important to check that we have all the necessary modes, otherwise
// setting modes will fail with ambiguous message.
//
// Duplicated devices can have different target modes (monitor) with different refresh rate,
// however this does not apply to the source mode (frame buffer?) and they must have same
// resolution.
//
// Without SDC_VIRTUAL_MODE_AWARE, devices would share the same source mode entry, but now
// they have separate entries that are more or less identical.
//
// To avoid surprising end-user with unexpected source mode change, we validate that all duplicate
// devices were provided instead of guessing modes automatically. This also resolve the problem of
// having to choose refresh rate for duplicate display - leave it to the end-user of this function...
const auto keys_view { std::ranges::views::keys(modes) };
const std::set<std::string> device_ids { std::begin(keys_view), std::end(keys_view) };
const auto all_device_ids { win_utils::getAllDeviceIdsAndMatchingDuplicates(*m_w_api, device_ids) };
if (all_device_ids.empty()) {
DD_LOG(error) << "Failed to get all duplicated devices!";
return false;
}

if (all_device_ids.size() != device_ids.size()) {
DD_LOG(error) << "Not all modes for duplicate displays were provided!";
return false;
}

const auto &original_data { m_w_api->queryDisplayConfig(QueryType::All) };
if (!original_data) {
// Error already logged
return false;
}

if (!doSetModes(*m_w_api, modes, Strategy::Relaxed)) {
// Error already logged
return false;
}

const auto all_modes_match = [&modes](const DeviceDisplayModeMap &current_modes) {
for (const auto &[device_id, requested_mode] : modes) {
auto mode_it { current_modes.find(device_id) };
if (mode_it == std::end(current_modes)) {
// This is a sanity check as `getCurrentDisplayModes` implicitly verifies this already.
return false;
}

if (!win_utils::fuzzyCompareModes(mode_it->second, requested_mode)) {
return false;
}
}

return true;
};

auto current_modes { getCurrentDisplayModes(device_ids) };
if (!current_modes.empty()) {
if (all_modes_match(current_modes)) {
return true;
}

// We have a problem when using SetDisplayConfig with SDC_ALLOW_CHANGES
// where it decides to use our new mode merely as a suggestion.
//
// This is good, since we don't have to be very precise with refresh rate,
// but also bad since it can just ignore our specified mode.
//
// However, it is possible that the user has created a custom display mode
// which is not exposed to the via Windows settings app. To allow this
// resolution to be selected, we actually need to omit SDC_ALLOW_CHANGES
// flag.
DD_LOG(info) << "Failed to change display modes using Windows recommended modes, trying to set modes more strictly!";
if (doSetModes(*m_w_api, modes, Strategy::Strict)) {
current_modes = getCurrentDisplayModes(device_ids);
if (!current_modes.empty() && all_modes_match(current_modes)) {
return true;
}
}
}

const UINT32 flags { SDC_APPLY | SDC_USE_SUPPLIED_DISPLAY_CONFIG | SDC_SAVE_TO_DATABASE | SDC_VIRTUAL_MODE_AWARE };
static_cast<void>(m_w_api->setDisplayConfig(original_data->m_paths, original_data->m_modes, flags)); // Return value does not matter as we are trying out best to undo
DD_LOG(error) << "Failed to set display mode(-s) completely!";
return false;
}
} // namespace display_device
23 changes: 12 additions & 11 deletions src/windows/windisplaydevicetopology.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,20 +15,14 @@ namespace display_device {
* @see set_topology for a description as this was split off to reduce cognitive complexity.
*/
bool
doSetTopology(WinApiLayerInterface &w_api, const ActiveTopology &new_topology) {
auto display_data { w_api.queryDisplayConfig(QueryType::All) };
if (!display_data) {
// Error already logged
return false;
}

const auto path_data { win_utils::collectSourceDataForMatchingPaths(w_api, display_data->m_paths) };
doSetTopology(WinApiLayerInterface &w_api, const ActiveTopology &new_topology, const PathAndModeData &display_data) {
const auto path_data { win_utils::collectSourceDataForMatchingPaths(w_api, display_data.m_paths) };
if (path_data.empty()) {
// Error already logged
return false;
}

auto paths { win_utils::makePathsForNewTopology(new_topology, path_data, display_data->m_paths) };
auto paths { win_utils::makePathsForNewTopology(new_topology, path_data, display_data.m_paths) };
if (paths.empty()) {
// Error already logged
return false;
Expand DownExpand Up@@ -161,7 +155,13 @@ namespace display_device {
return true;
}

if (doSetTopology(*m_w_api, new_topology)) {
const auto &original_data { m_w_api->queryDisplayConfig(QueryType::All) };
if (!original_data) {
// Error already logged
return false;
}

if (doSetTopology(*m_w_api, new_topology, *original_data)) {
const auto updated_topology { getCurrentTopology() };
if (isTopologyValid(updated_topology)) {
if (isTopologyTheSame(new_topology, updated_topology)) {
Expand DownExpand Up@@ -200,7 +200,8 @@ namespace display_device {
}

// Revert back to the original topology
doSetTopology(*m_w_api, current_topology); // Return value does not matter
const UINT32 flags { SDC_APPLY | SDC_USE_SUPPLIED_DISPLAY_CONFIG | SDC_SAVE_TO_DATABASE | SDC_VIRTUAL_MODE_AWARE };
static_cast<void>(m_w_api->setDisplayConfig(original_data->m_paths, original_data->m_modes, flags)); // Return value does not matter as we are trying out best to undo
}

return false;
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
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
5 changes: 5 additions & 0 deletions src/windows/include/displaydevice/windows/types.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -86,4 +86,9 @@ namespace display_device {
Rational m_refresh_rate;
};

/**
* @brief Ordered map of [DEVICE_ID -> DisplayMode].
*/
using DeviceDisplayModeMap = std::map<std::string, DisplayMode>;

} // namespace display_device
16 changes: 12 additions & 4 deletions src/windows/include/displaydevice/windows/windisplaydevice.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,22 +19,30 @@ namespace display_device {
*/
explicit WinDisplayDevice(std::shared_ptr<WinApiLayerInterface> w_api);

/** For details @see WinDisplayDevice::getCurrentTopology */
/** For details @see WinDisplayDeviceInterface::getCurrentTopology */
[[nodiscard]] ActiveTopology
getCurrentTopology() const override;

/** For details @see WinDisplayDevice::isTopologyValid */
/** For details @see WinDisplayDeviceInterface::isTopologyValid */
[[nodiscard]] bool
isTopologyValid(const ActiveTopology &topology) const override;

/** For details @see WinDisplayDevice::getCurrentTopology */
/** For details @see WinDisplayDeviceInterface::getCurrentTopology */
[[nodiscard]] bool
isTopologyTheSame(const ActiveTopology &lhs, const ActiveTopology &rhs) const override;

/** For details @see WinDisplayDevice::setTopology */
/** For details @see WinDisplayDeviceInterface::setTopology */
[[nodiscard]] bool
setTopology(const ActiveTopology &new_topology) override;

/** For details @see WinDisplayDeviceInterface::getCurrentDisplayModes */
[[nodiscard]] DeviceDisplayModeMap
getCurrentDisplayModes(const std::set<std::string> &device_ids) const override;

/** For details @see WinDisplayDeviceInterface::setDisplayModes */
[[nodiscard]] bool
setDisplayModes(const DeviceDisplayModeMap &modes) override;

private:
std::shared_ptr<WinApiLayerInterface> m_w_api;
};
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
#pragma once

// system includes
#include <set>

// local includes
#include "displaydevice/windows/types.h"

Expand DownExpand Up@@ -73,12 +76,48 @@ namespace display_device {
*
* EXAMPLES:
* ```cpp
* auto current_topology { getCurrentTopology() };
* const WinDisplayDeviceInterface* iface = getIface(...);
* auto current_topology { iface->getCurrentTopology() };
* // Modify the current_topology
* const bool success = setTopology(current_topology);
* const bool success = iface->setTopology(current_topology);
* ```
*/
[[nodiscard]] virtual bool
setTopology(const ActiveTopology &new_topology) = 0;

/**
* @brief Get current display modes for the devices.
* @param device_ids A list of devices to get the modes for.
* @returns A map of device modes per a device or an empty map if a mode could not be found (e.g. device is inactive).
* Empty map can also be returned if an error has occurred.
*
* EXAMPLES:
* ```cpp
* const WinDisplayDeviceInterface* iface = getIface(...);
* const std::set<std::string> device_ids { "DEVICE_ID_1", "DEVICE_ID_2" };
* const auto current_modes = iface->getCurrentDisplayModes(device_ids);
* ```
*/
[[nodiscard]] virtual DeviceDisplayModeMap
getCurrentDisplayModes(const std::set<std::string> &device_ids) const = 0;

/**
* @brief Set new display modes for the devices.
* @param modes A map of modes to set.
* @returns True if modes were set, false otherwise.
* @warning if any of the specified devices are duplicated, modes modes be provided
* for duplicates too!
*
* EXAMPLES:
* ```cpp
* const WinDisplayDeviceInterface* iface = getIface(...);
* const std::string display_a { "MY_ID_1" };
* const std::string display_b { "MY_ID_2" };
* const auto success = iface->setDisplayModes({ { display_a, { { 1920, 1080 }, { 60, 1 } } },
* { display_b, { { 1920, 1080 }, { 120, 1 } } } });
* ```
*/
[[nodiscard]] virtual bool
setDisplayModes(const DeviceDisplayModeMap &modes) = 0;
};
} // namespace display_device
236 changes: 236 additions & 0 deletions src/windows/windisplaydevicemodes.cpp
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
// class header include
#include "displaydevice/windows/windisplaydevice.h"

// system includes
#include <ranges>

// local includes
#include "displaydevice/logging.h"
#include "displaydevice/windows/winapiutils.h"

namespace display_device {
namespace {

/**
* @brief Strategy to be used when changing display modes.
*/
enum class Strategy {
Relaxed,
Strict
};

/**
* @see set_display_modes for a description as this was split off to reduce cognitive complexity.
*/
bool
doSetModes(WinApiLayerInterface &w_api, const DeviceDisplayModeMap &modes, const Strategy strategy) {
auto display_data { w_api.queryDisplayConfig(QueryType::Active) };
if (!display_data) {
// Error already logged
return false;
}

bool changes_applied { false };
for (const auto &[device_id, mode] : modes) {
const auto path { win_utils::getActivePath(w_api, device_id, display_data->m_paths) };
if (!path) {
DD_LOG(error) << "Failed to find device for " << device_id << "!";
return false;
}

const auto source_mode { win_utils::getSourceMode(win_utils::getSourceIndex(*path, display_data->m_modes), display_data->m_modes) };
if (!source_mode) {
DD_LOG(error) << "Active device does not have a source mode: " << device_id << "!";
return false;
}

bool new_changes { false };
const bool resolution_changed { source_mode->width != mode.m_resolution.m_width || source_mode->height != mode.m_resolution.m_height };

bool refresh_rate_changed;
if (strategy == Strategy::Relaxed) {
refresh_rate_changed = !win_utils::fuzzyCompareRefreshRates(Rational { path->targetInfo.refreshRate.Numerator, path->targetInfo.refreshRate.Denominator }, mode.m_refresh_rate);
}
else {
// Since we are in strict mode, do not fuzzy compare it
refresh_rate_changed = path->targetInfo.refreshRate.Numerator != mode.m_refresh_rate.m_numerator ||
path->targetInfo.refreshRate.Denominator != mode.m_refresh_rate.m_denominator;
}

if (resolution_changed) {
source_mode->width = mode.m_resolution.m_width;
source_mode->height = mode.m_resolution.m_height;
new_changes = true;
}

if (refresh_rate_changed) {
path->targetInfo.refreshRate = { mode.m_refresh_rate.m_numerator, mode.m_refresh_rate.m_denominator };
new_changes = true;
}

if (new_changes) {
// Clear the target index so that Windows has to select/modify the target to best match the requirements.
win_utils::setTargetIndex(*path, std::nullopt);
win_utils::setDesktopIndex(*path, std::nullopt); // Part of struct containing target index and so it needs to be cleared
}

changes_applied = changes_applied || new_changes;
}

if (!changes_applied) {
DD_LOG(debug) << "No changes were made to display modes as they are equal.";
return true;
}

UINT32 flags { SDC_APPLY | SDC_USE_SUPPLIED_DISPLAY_CONFIG | SDC_SAVE_TO_DATABASE | SDC_VIRTUAL_MODE_AWARE };
if (strategy == Strategy::Relaxed) {
// It's probably best for Windows to select the "best" display settings for us. However, in case we
// have custom resolution set in nvidia control panel for example, this flag will prevent successfully applying
// settings to it.
flags |= SDC_ALLOW_CHANGES;
}

const LONG result { w_api.setDisplayConfig(display_data->m_paths, display_data->m_modes, flags) };
if (result != ERROR_SUCCESS) {
DD_LOG(error) << w_api.getErrorString(result) << " failed to set display mode!";
return false;
}

return true;
}
} // namespace

DeviceDisplayModeMap
WinDisplayDevice::getCurrentDisplayModes(const std::set<std::string> &device_ids) const {
if (device_ids.empty()) {
DD_LOG(error) << "Device id set is empty!";
return {};
}

const auto display_data { m_w_api->queryDisplayConfig(QueryType::Active) };
if (!display_data) {
// Error already logged
return {};
}

DeviceDisplayModeMap current_modes;
for (const auto &device_id : device_ids) {
if (device_id.empty()) {
DD_LOG(error) << "Device id is empty!";
return {};
}

const auto path { win_utils::getActivePath(*m_w_api, device_id, display_data->m_paths) };
if (!path) {
DD_LOG(error) << "Failed to find device for " << device_id << "!";
return {};
}

const auto source_mode { win_utils::getSourceMode(win_utils::getSourceIndex(*path, display_data->m_modes), display_data->m_modes) };
if (!source_mode) {
DD_LOG(error) << "Active device does not have a source mode: " << device_id << "!";
return {};
}

// For whatever reason they put refresh rate into path, but not the resolution.
const auto target_refresh_rate { path->targetInfo.refreshRate };
current_modes[device_id] = DisplayMode {
{ source_mode->width, source_mode->height },
{ target_refresh_rate.Numerator, target_refresh_rate.Denominator }
};
}

return current_modes;
}

bool
WinDisplayDevice::setDisplayModes(const DeviceDisplayModeMap &modes) {
if (modes.empty()) {
DD_LOG(error) << "Modes map is empty!";
return false;
}

// Here it is important to check that we have all the necessary modes, otherwise
// setting modes will fail with ambiguous message.
//
// Duplicated devices can have different target modes (monitor) with different refresh rate,
// however this does not apply to the source mode (frame buffer?) and they must have same
// resolution.
//
// Without SDC_VIRTUAL_MODE_AWARE, devices would share the same source mode entry, but now
// they have separate entries that are more or less identical.
//
// To avoid surprising end-user with unexpected source mode change, we validate that all duplicate
// devices were provided instead of guessing modes automatically. This also resolve the problem of
// having to choose refresh rate for duplicate display - leave it to the end-user of this function...
const auto keys_view { std::ranges::views::keys(modes) };
const std::set<std::string> device_ids { std::begin(keys_view), std::end(keys_view) };
const auto all_device_ids { win_utils::getAllDeviceIdsAndMatchingDuplicates(*m_w_api, device_ids) };
if (all_device_ids.empty()) {
DD_LOG(error) << "Failed to get all duplicated devices!";
return false;
}

if (all_device_ids.size() != device_ids.size()) {
DD_LOG(error) << "Not all modes for duplicate displays were provided!";
return false;
}

const auto &original_data { m_w_api->queryDisplayConfig(QueryType::All) };
if (!original_data) {
// Error already logged
return false;
}

if (!doSetModes(*m_w_api, modes, Strategy::Relaxed)) {
// Error already logged
return false;
}

const auto all_modes_match = [&modes](const DeviceDisplayModeMap &current_modes) {
for (const auto &[device_id, requested_mode] : modes) {
auto mode_it { current_modes.find(device_id) };
if (mode_it == std::end(current_modes)) {
// This is a sanity check as `getCurrentDisplayModes` implicitly verifies this already.
return false;
}

if (!win_utils::fuzzyCompareModes(mode_it->second, requested_mode)) {
return false;
}
}

return true;
};

auto current_modes { getCurrentDisplayModes(device_ids) };
if (!current_modes.empty()) {
if (all_modes_match(current_modes)) {
return true;
}

// We have a problem when using SetDisplayConfig with SDC_ALLOW_CHANGES
// where it decides to use our new mode merely as a suggestion.
//
// This is good, since we don't have to be very precise with refresh rate,
// but also bad since it can just ignore our specified mode.
//
// However, it is possible that the user has created a custom display mode
// which is not exposed to the via Windows settings app. To allow this
// resolution to be selected, we actually need to omit SDC_ALLOW_CHANGES
// flag.
DD_LOG(info) << "Failed to change display modes using Windows recommended modes, trying to set modes more strictly!";
if (doSetModes(*m_w_api, modes, Strategy::Strict)) {
current_modes = getCurrentDisplayModes(device_ids);
if (!current_modes.empty() && all_modes_match(current_modes)) {
return true;
}
}
}

const UINT32 flags { SDC_APPLY | SDC_USE_SUPPLIED_DISPLAY_CONFIG | SDC_SAVE_TO_DATABASE | SDC_VIRTUAL_MODE_AWARE };
static_cast<void>(m_w_api->setDisplayConfig(original_data->m_paths, original_data->m_modes, flags)); // Return value does not matter as we are trying out best to undo
DD_LOG(error) << "Failed to set display mode(-s) completely!";
return false;
}
} // namespace display_device
23 changes: 12 additions & 11 deletions src/windows/windisplaydevicetopology.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,20 +15,14 @@ namespace display_device {
* @see set_topology for a description as this was split off to reduce cognitive complexity.
*/
bool
doSetTopology(WinApiLayerInterface &w_api, const ActiveTopology &new_topology) {
auto display_data { w_api.queryDisplayConfig(QueryType::All) };
if (!display_data) {
// Error already logged
return false;
}

const auto path_data { win_utils::collectSourceDataForMatchingPaths(w_api, display_data->m_paths) };
doSetTopology(WinApiLayerInterface &w_api, const ActiveTopology &new_topology, const PathAndModeData &display_data) {
const auto path_data { win_utils::collectSourceDataForMatchingPaths(w_api, display_data.m_paths) };
if (path_data.empty()) {
// Error already logged
return false;
}

auto paths { win_utils::makePathsForNewTopology(new_topology, path_data, display_data->m_paths) };
auto paths { win_utils::makePathsForNewTopology(new_topology, path_data, display_data.m_paths) };
if (paths.empty()) {
// Error already logged
return false;
Expand DownExpand Up@@ -161,7 +155,13 @@ namespace display_device {
return true;
}

if (doSetTopology(*m_w_api, new_topology)) {
const auto &original_data { m_w_api->queryDisplayConfig(QueryType::All) };
if (!original_data) {
// Error already logged
return false;
}

if (doSetTopology(*m_w_api, new_topology, *original_data)) {
const auto updated_topology { getCurrentTopology() };
if (isTopologyValid(updated_topology)) {
if (isTopologyTheSame(new_topology, updated_topology)) {
Expand DownExpand Up@@ -200,7 +200,8 @@ namespace display_device {
}

// Revert back to the original topology
doSetTopology(*m_w_api, current_topology); // Return value does not matter
const UINT32 flags { SDC_APPLY | SDC_USE_SUPPLIED_DISPLAY_CONFIG | SDC_SAVE_TO_DATABASE | SDC_VIRTUAL_MODE_AWARE };
static_cast<void>(m_w_api->setDisplayConfig(original_data->m_paths, original_data->m_modes, flags)); // Return value does not matter as we are trying out best to undo
}

return false;
Expand Down
Loading