Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitmodules
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
[submodule "third-party/boost"]
path = third-party/boost
url = https://github.com/boostorg/boost.git
branch = master
[submodule "third-party/googletest"]
path = third-party/googletest
url = https://github.com/google/googletest.git
Expand Down
3 changes: 3 additions & 0 deletions CMakeLists.txt
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,6 +15,9 @@ if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build." FORCE)
endif()

# Add our custom CMake modules to the global path
list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)

#
# Project optional configuration
#
Expand Down
28 changes: 28 additions & 0 deletions cmake/Boost_DD.cmake
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
#
# Loads the boost library giving the priority to the system package first, with a fallback
# to the submodule.
#
include_guard(GLOBAL)

find_package(Boost 1.85)
if(NOT Boost_FOUND)
message(STATUS "Boost v1.85.x package not found in system. Falling back to submodule.")

# Limit boost to the required libraries only
set(BOOST_INCLUDE_LIBRARIES
algorithm
scope
uuid)
add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/../third-party/boost third-party/boost EXCLUDE_FROM_ALL)

# Emulate the "find_package" target
add_library(Boost::boost INTERFACE IMPORTED)

file(GLOB BOOST_LIBS CONFIGURE_DEPENDS "${CMAKE_CURRENT_LIST_DIR}/../third-party/boost/libs/*")
foreach(lib_path ${BOOST_LIBS})
get_filename_component(lib_dir "${lib_path}" NAME)
if(TARGET "Boost::${lib_dir}")
target_link_libraries(Boost::boost INTERFACE "Boost::${lib_dir}")
endif()
endforeach()
endif()
5 changes: 4 additions & 1 deletion src/windows/CMakeLists.txt
Original file line numberDiff line numberDiff line change
Expand Up@@ -11,5 +11,8 @@ add_library(${MODULE} ${HEADER_LIST} ${SOURCE_LIST})
# Provide the includes together with this library
target_include_directories(${MODULE} PUBLIC include)

# Additional external libraries
include(Boost_DD)

# Link the additional libraries
target_link_libraries(${MODULE} PRIVATE libcommon)
target_link_libraries(${MODULE} PRIVATE libcommon Boost::boost setupapi)
16 changes: 16 additions & 0 deletions src/windows/include/displaydevice/windows/winapilayer.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,5 +16,21 @@ namespace display_device {
/** For details @see WinApiLayerInterface::query_display_config */
[[nodiscard]] std::optional<path_and_mode_data_t>
query_display_config(query_type_e type) const override;

/** For details @see WinApiLayerInterface::get_device_id */
[[nodiscard]] std::string
get_device_id(const DISPLAYCONFIG_PATH_INFO &path) const override;

/** For details @see WinApiLayerInterface::get_monitor_device_path */
[[nodiscard]] std::string
get_monitor_device_path(const DISPLAYCONFIG_PATH_INFO &path) const override;

/** For details @see WinApiLayerInterface::get_friendly_name */
[[nodiscard]] std::string
get_friendly_name(const DISPLAYCONFIG_PATH_INFO &path) const override;

/** For details @see WinApiLayerInterface::get_display_name */
[[nodiscard]] std::string
get_display_name(const DISPLAYCONFIG_PATH_INFO &path) const override;
};
} // namespace display_device
105 changes: 105 additions & 0 deletions src/windows/include/displaydevice/windows/winapilayerinterface.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -62,5 +62,110 @@ namespace display_device {
*/
[[nodiscard]] virtual std::optional<path_and_mode_data_t>
query_display_config(query_type_e type) const = 0;

/**
* @brief Get a stable and persistent device id for the path.
*
* This function tries to generate a unique id for the path that
* is persistent between driver re-installs and physical unplugging and
* replugging of the device.
*
* The best candidate for it could have been a "ContainerID" from the
* registry, however it was found to be unstable for the virtual display
* (probably because it uses the EDID for the id generation and the current
* virtual displays have incomplete EDID information). The "ContainerID"
* also does not change if the physical device is plugged into a different
* port and seems to be very stable, however because of virtual displays
* other solution was used.
*
* The accepted solution was to use the "InstanceID" and EDID (just to be
* on the safe side). "InstanceID" is semi-stable, it has some parts that
* change between driver re-installs and it has a part that changes based
* on the GPU port that the display is connected to. It is most likely to
* be unique, but since the MS documentation is lacking we are also hashing
* EDID information (contains serial ids, timestamps and etc. that should
* guarantee that identical displays are differentiated like with the
* "ContainerID"). Most importantly this information is stable for the virtual
* displays.
*
* After we remove the unstable parts from the "InstanceID" and hash everything
* together, we get an id that changes only when you connect the display to
* a different GPU port which seems to be acceptable.
*
* As a fallback we are using a hashed device path, in case the "InstanceID" or
* EDID is not available. At least if you don't do driver re-installs often
* and change the GPU ports, it will be stable for a while.
*
* @param path Path to get the device id for.
* @returns Device id, or an empty string if it could not be generated.
* @see query_display_config on how to get paths from the system.
*
* EXAMPLES:
* ```cpp
* DISPLAYCONFIG_PATH_INFO path;
* const WinApiLayerInterface* iface = getIface(...);
* const std::string device_path = iface->get_device_id(path);
* ```
*/
[[nodiscard]] virtual std::string
get_device_id(const DISPLAYCONFIG_PATH_INFO &path) const = 0;

/**
* @brief Get a string that represents a path from the adapter to the display target.
* @param path Path to get the string for.
* @returns String representation, or an empty string if it's not available.
* @see query_display_config on how to get paths from the system.
* @note In the rest of the code we refer to this string representation simply as the "device path".
* It is used as a simple way of grouping related path objects together and removing "bad" paths
* that don't have such string representation.
*
* EXAMPLES:
* ```cpp
* DISPLAYCONFIG_PATH_INFO path;
* const WinApiLayerInterface* iface = getIface(...);
* const std::string device_path = iface->get_monitor_device_path(path);
* ```
*/
[[nodiscard]] virtual std::string
get_monitor_device_path(const DISPLAYCONFIG_PATH_INFO &path) const = 0;

/**
* @brief Get the user friendly name for the path.
* @param path Path to get user friendly name for.
* @returns User friendly name for the path if available, empty string otherwise.
* @see query_display_config on how to get paths from the system.
* @note This is usually a monitor name (like "ROG PG279Q") and is most likely taken from EDID.
*
* EXAMPLES:
* ```cpp
* DISPLAYCONFIG_PATH_INFO path;
* const WinApiLayerInterface* iface = getIface(...);
* const std::string friendly_name = iface->get_friendly_name(path);
* ```
*/
[[nodiscard]] virtual std::string
get_friendly_name(const DISPLAYCONFIG_PATH_INFO &path) const = 0;

/**
* @brief Get the logical display name for the path.
*
* These are the "\\\\.\\DISPLAY1", "\\\\.\\DISPLAY2" and etc. display names that can
* change whenever Windows wants to change them.
*
* @param path Path to get user display name for.
* @returns Display name for the path if available, empty string otherwise.
* @see query_display_config on how to get paths from the system.
* @note Inactive paths can have these names already assigned to them, even
* though they are not even in use! There can also be duplicates.
*
* EXAMPLES:
* ```cpp
* DISPLAYCONFIG_PATH_INFO path;
* const WinApiLayerInterface* iface = getIface(...);
* const std::string display_name = iface->get_display_name(path);
* ```
*/
[[nodiscard]] virtual std::string
get_display_name(const DISPLAYCONFIG_PATH_INFO &path) const = 0;
};
} // namespace display_device
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
4 changes: 4 additions & 0 deletions .gitmodules
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
[submodule "third-party/boost"]
path = third-party/boost
url = https://github.com/boostorg/boost.git
branch = master
[submodule "third-party/googletest"]
path = third-party/googletest
url = https://github.com/google/googletest.git
Expand Down
3 changes: 3 additions & 0 deletions CMakeLists.txt
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,6 +15,9 @@ if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build." FORCE)
endif()

# Add our custom CMake modules to the global path
list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)

#
# Project optional configuration
#
Expand Down
28 changes: 28 additions & 0 deletions cmake/Boost_DD.cmake
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
#
# Loads the boost library giving the priority to the system package first, with a fallback
# to the submodule.
#
include_guard(GLOBAL)

find_package(Boost 1.85)
if(NOT Boost_FOUND)
message(STATUS "Boost v1.85.x package not found in system. Falling back to submodule.")

# Limit boost to the required libraries only
set(BOOST_INCLUDE_LIBRARIES
algorithm
scope
uuid)
add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/../third-party/boost third-party/boost EXCLUDE_FROM_ALL)

# Emulate the "find_package" target
add_library(Boost::boost INTERFACE IMPORTED)

file(GLOB BOOST_LIBS CONFIGURE_DEPENDS "${CMAKE_CURRENT_LIST_DIR}/../third-party/boost/libs/*")
foreach(lib_path ${BOOST_LIBS})
get_filename_component(lib_dir "${lib_path}" NAME)
if(TARGET "Boost::${lib_dir}")
target_link_libraries(Boost::boost INTERFACE "Boost::${lib_dir}")
endif()
endforeach()
endif()
5 changes: 4 additions & 1 deletion src/windows/CMakeLists.txt
Original file line numberDiff line numberDiff line change
Expand Up@@ -11,5 +11,8 @@ add_library(${MODULE} ${HEADER_LIST} ${SOURCE_LIST})
# Provide the includes together with this library
target_include_directories(${MODULE} PUBLIC include)

# Additional external libraries
include(Boost_DD)

# Link the additional libraries
target_link_libraries(${MODULE} PRIVATE libcommon)
target_link_libraries(${MODULE} PRIVATE libcommon Boost::boost setupapi)
16 changes: 16 additions & 0 deletions src/windows/include/displaydevice/windows/winapilayer.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,5 +16,21 @@ namespace display_device {
/** For details @see WinApiLayerInterface::query_display_config */
[[nodiscard]] std::optional<path_and_mode_data_t>
query_display_config(query_type_e type) const override;

/** For details @see WinApiLayerInterface::get_device_id */
[[nodiscard]] std::string
get_device_id(const DISPLAYCONFIG_PATH_INFO &path) const override;

/** For details @see WinApiLayerInterface::get_monitor_device_path */
[[nodiscard]] std::string
get_monitor_device_path(const DISPLAYCONFIG_PATH_INFO &path) const override;

/** For details @see WinApiLayerInterface::get_friendly_name */
[[nodiscard]] std::string
get_friendly_name(const DISPLAYCONFIG_PATH_INFO &path) const override;

/** For details @see WinApiLayerInterface::get_display_name */
[[nodiscard]] std::string
get_display_name(const DISPLAYCONFIG_PATH_INFO &path) const override;
};
} // namespace display_device
105 changes: 105 additions & 0 deletions src/windows/include/displaydevice/windows/winapilayerinterface.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -62,5 +62,110 @@ namespace display_device {
*/
[[nodiscard]] virtual std::optional<path_and_mode_data_t>
query_display_config(query_type_e type) const = 0;

/**
* @brief Get a stable and persistent device id for the path.
*
* This function tries to generate a unique id for the path that
* is persistent between driver re-installs and physical unplugging and
* replugging of the device.
*
* The best candidate for it could have been a "ContainerID" from the
* registry, however it was found to be unstable for the virtual display
* (probably because it uses the EDID for the id generation and the current
* virtual displays have incomplete EDID information). The "ContainerID"
* also does not change if the physical device is plugged into a different
* port and seems to be very stable, however because of virtual displays
* other solution was used.
*
* The accepted solution was to use the "InstanceID" and EDID (just to be
* on the safe side). "InstanceID" is semi-stable, it has some parts that
* change between driver re-installs and it has a part that changes based
* on the GPU port that the display is connected to. It is most likely to
* be unique, but since the MS documentation is lacking we are also hashing
* EDID information (contains serial ids, timestamps and etc. that should
* guarantee that identical displays are differentiated like with the
* "ContainerID"). Most importantly this information is stable for the virtual
* displays.
*
* After we remove the unstable parts from the "InstanceID" and hash everything
* together, we get an id that changes only when you connect the display to
* a different GPU port which seems to be acceptable.
*
* As a fallback we are using a hashed device path, in case the "InstanceID" or
* EDID is not available. At least if you don't do driver re-installs often
* and change the GPU ports, it will be stable for a while.
*
* @param path Path to get the device id for.
* @returns Device id, or an empty string if it could not be generated.
* @see query_display_config on how to get paths from the system.
*
* EXAMPLES:
* ```cpp
* DISPLAYCONFIG_PATH_INFO path;
* const WinApiLayerInterface* iface = getIface(...);
* const std::string device_path = iface->get_device_id(path);
* ```
*/
[[nodiscard]] virtual std::string
get_device_id(const DISPLAYCONFIG_PATH_INFO &path) const = 0;

/**
* @brief Get a string that represents a path from the adapter to the display target.
* @param path Path to get the string for.
* @returns String representation, or an empty string if it's not available.
* @see query_display_config on how to get paths from the system.
* @note In the rest of the code we refer to this string representation simply as the "device path".
* It is used as a simple way of grouping related path objects together and removing "bad" paths
* that don't have such string representation.
*
* EXAMPLES:
* ```cpp
* DISPLAYCONFIG_PATH_INFO path;
* const WinApiLayerInterface* iface = getIface(...);
* const std::string device_path = iface->get_monitor_device_path(path);
* ```
*/
[[nodiscard]] virtual std::string
get_monitor_device_path(const DISPLAYCONFIG_PATH_INFO &path) const = 0;

/**
* @brief Get the user friendly name for the path.
* @param path Path to get user friendly name for.
* @returns User friendly name for the path if available, empty string otherwise.
* @see query_display_config on how to get paths from the system.
* @note This is usually a monitor name (like "ROG PG279Q") and is most likely taken from EDID.
*
* EXAMPLES:
* ```cpp
* DISPLAYCONFIG_PATH_INFO path;
* const WinApiLayerInterface* iface = getIface(...);
* const std::string friendly_name = iface->get_friendly_name(path);
* ```
*/
[[nodiscard]] virtual std::string
get_friendly_name(const DISPLAYCONFIG_PATH_INFO &path) const = 0;

/**
* @brief Get the logical display name for the path.
*
* These are the "\\\\.\\DISPLAY1", "\\\\.\\DISPLAY2" and etc. display names that can
* change whenever Windows wants to change them.
*
* @param path Path to get user display name for.
* @returns Display name for the path if available, empty string otherwise.
* @see query_display_config on how to get paths from the system.
* @note Inactive paths can have these names already assigned to them, even
* though they are not even in use! There can also be duplicates.
*
* EXAMPLES:
* ```cpp
* DISPLAYCONFIG_PATH_INFO path;
* const WinApiLayerInterface* iface = getIface(...);
* const std::string display_name = iface->get_display_name(path);
* ```
*/
[[nodiscard]] virtual std::string
get_display_name(const DISPLAYCONFIG_PATH_INFO &path) const = 0;
};
} // namespace display_device
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
4 changes: 4 additions & 0 deletions .gitmodules
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
[submodule "third-party/boost"]
path = third-party/boost
url = https://github.com/boostorg/boost.git
branch = master
[submodule "third-party/googletest"]
path = third-party/googletest
url = https://github.com/google/googletest.git
Expand Down
3 changes: 3 additions & 0 deletions CMakeLists.txt
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,6 +15,9 @@ if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build." FORCE)
endif()

# Add our custom CMake modules to the global path
list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)

#
# Project optional configuration
#
Expand Down
28 changes: 28 additions & 0 deletions cmake/Boost_DD.cmake
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
#
# Loads the boost library giving the priority to the system package first, with a fallback
# to the submodule.
#
include_guard(GLOBAL)

find_package(Boost 1.85)
if(NOT Boost_FOUND)
message(STATUS "Boost v1.85.x package not found in system. Falling back to submodule.")

# Limit boost to the required libraries only
set(BOOST_INCLUDE_LIBRARIES
algorithm
scope
uuid)
add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/../third-party/boost third-party/boost EXCLUDE_FROM_ALL)

# Emulate the "find_package" target
add_library(Boost::boost INTERFACE IMPORTED)

file(GLOB BOOST_LIBS CONFIGURE_DEPENDS "${CMAKE_CURRENT_LIST_DIR}/../third-party/boost/libs/*")
foreach(lib_path ${BOOST_LIBS})
get_filename_component(lib_dir "${lib_path}" NAME)
if(TARGET "Boost::${lib_dir}")
target_link_libraries(Boost::boost INTERFACE "Boost::${lib_dir}")
endif()
endforeach()
endif()
5 changes: 4 additions & 1 deletion src/windows/CMakeLists.txt
Original file line numberDiff line numberDiff line change
Expand Up@@ -11,5 +11,8 @@ add_library(${MODULE} ${HEADER_LIST} ${SOURCE_LIST})
# Provide the includes together with this library
target_include_directories(${MODULE} PUBLIC include)

# Additional external libraries
include(Boost_DD)

# Link the additional libraries
target_link_libraries(${MODULE} PRIVATE libcommon)
target_link_libraries(${MODULE} PRIVATE libcommon Boost::boost setupapi)
16 changes: 16 additions & 0 deletions src/windows/include/displaydevice/windows/winapilayer.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,5 +16,21 @@ namespace display_device {
/** For details @see WinApiLayerInterface::query_display_config */
[[nodiscard]] std::optional<path_and_mode_data_t>
query_display_config(query_type_e type) const override;

/** For details @see WinApiLayerInterface::get_device_id */
[[nodiscard]] std::string
get_device_id(const DISPLAYCONFIG_PATH_INFO &path) const override;

/** For details @see WinApiLayerInterface::get_monitor_device_path */
[[nodiscard]] std::string
get_monitor_device_path(const DISPLAYCONFIG_PATH_INFO &path) const override;

/** For details @see WinApiLayerInterface::get_friendly_name */
[[nodiscard]] std::string
get_friendly_name(const DISPLAYCONFIG_PATH_INFO &path) const override;

/** For details @see WinApiLayerInterface::get_display_name */
[[nodiscard]] std::string
get_display_name(const DISPLAYCONFIG_PATH_INFO &path) const override;
};
} // namespace display_device
105 changes: 105 additions & 0 deletions src/windows/include/displaydevice/windows/winapilayerinterface.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -62,5 +62,110 @@ namespace display_device {
*/
[[nodiscard]] virtual std::optional<path_and_mode_data_t>
query_display_config(query_type_e type) const = 0;

/**
* @brief Get a stable and persistent device id for the path.
*
* This function tries to generate a unique id for the path that
* is persistent between driver re-installs and physical unplugging and
* replugging of the device.
*
* The best candidate for it could have been a "ContainerID" from the
* registry, however it was found to be unstable for the virtual display
* (probably because it uses the EDID for the id generation and the current
* virtual displays have incomplete EDID information). The "ContainerID"
* also does not change if the physical device is plugged into a different
* port and seems to be very stable, however because of virtual displays
* other solution was used.
*
* The accepted solution was to use the "InstanceID" and EDID (just to be
* on the safe side). "InstanceID" is semi-stable, it has some parts that
* change between driver re-installs and it has a part that changes based
* on the GPU port that the display is connected to. It is most likely to
* be unique, but since the MS documentation is lacking we are also hashing
* EDID information (contains serial ids, timestamps and etc. that should
* guarantee that identical displays are differentiated like with the
* "ContainerID"). Most importantly this information is stable for the virtual
* displays.
*
* After we remove the unstable parts from the "InstanceID" and hash everything
* together, we get an id that changes only when you connect the display to
* a different GPU port which seems to be acceptable.
*
* As a fallback we are using a hashed device path, in case the "InstanceID" or
* EDID is not available. At least if you don't do driver re-installs often
* and change the GPU ports, it will be stable for a while.
*
* @param path Path to get the device id for.
* @returns Device id, or an empty string if it could not be generated.
* @see query_display_config on how to get paths from the system.
*
* EXAMPLES:
* ```cpp
* DISPLAYCONFIG_PATH_INFO path;
* const WinApiLayerInterface* iface = getIface(...);
* const std::string device_path = iface->get_device_id(path);
* ```
*/
[[nodiscard]] virtual std::string
get_device_id(const DISPLAYCONFIG_PATH_INFO &path) const = 0;

/**
* @brief Get a string that represents a path from the adapter to the display target.
* @param path Path to get the string for.
* @returns String representation, or an empty string if it's not available.
* @see query_display_config on how to get paths from the system.
* @note In the rest of the code we refer to this string representation simply as the "device path".
* It is used as a simple way of grouping related path objects together and removing "bad" paths
* that don't have such string representation.
*
* EXAMPLES:
* ```cpp
* DISPLAYCONFIG_PATH_INFO path;
* const WinApiLayerInterface* iface = getIface(...);
* const std::string device_path = iface->get_monitor_device_path(path);
* ```
*/
[[nodiscard]] virtual std::string
get_monitor_device_path(const DISPLAYCONFIG_PATH_INFO &path) const = 0;

/**
* @brief Get the user friendly name for the path.
* @param path Path to get user friendly name for.
* @returns User friendly name for the path if available, empty string otherwise.
* @see query_display_config on how to get paths from the system.
* @note This is usually a monitor name (like "ROG PG279Q") and is most likely taken from EDID.
*
* EXAMPLES:
* ```cpp
* DISPLAYCONFIG_PATH_INFO path;
* const WinApiLayerInterface* iface = getIface(...);
* const std::string friendly_name = iface->get_friendly_name(path);
* ```
*/
[[nodiscard]] virtual std::string
get_friendly_name(const DISPLAYCONFIG_PATH_INFO &path) const = 0;

/**
* @brief Get the logical display name for the path.
*
* These are the "\\\\.\\DISPLAY1", "\\\\.\\DISPLAY2" and etc. display names that can
* change whenever Windows wants to change them.
*
* @param path Path to get user display name for.
* @returns Display name for the path if available, empty string otherwise.
* @see query_display_config on how to get paths from the system.
* @note Inactive paths can have these names already assigned to them, even
* though they are not even in use! There can also be duplicates.
*
* EXAMPLES:
* ```cpp
* DISPLAYCONFIG_PATH_INFO path;
* const WinApiLayerInterface* iface = getIface(...);
* const std::string display_name = iface->get_display_name(path);
* ```
*/
[[nodiscard]] virtual std::string
get_display_name(const DISPLAYCONFIG_PATH_INFO &path) const = 0;
};
} // namespace display_device
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
4 changes: 4 additions & 0 deletions .gitmodules
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
[submodule "third-party/boost"]
path = third-party/boost
url = https://github.com/boostorg/boost.git
branch = master
[submodule "third-party/googletest"]
path = third-party/googletest
url = https://github.com/google/googletest.git
Expand Down
3 changes: 3 additions & 0 deletions CMakeLists.txt
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,6 +15,9 @@ if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build." FORCE)
endif()

# Add our custom CMake modules to the global path
list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)

#
# Project optional configuration
#
Expand Down
28 changes: 28 additions & 0 deletions cmake/Boost_DD.cmake
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
#
# Loads the boost library giving the priority to the system package first, with a fallback
# to the submodule.
#
include_guard(GLOBAL)

find_package(Boost 1.85)
if(NOT Boost_FOUND)
message(STATUS "Boost v1.85.x package not found in system. Falling back to submodule.")

# Limit boost to the required libraries only
set(BOOST_INCLUDE_LIBRARIES
algorithm
scope
uuid)
add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/../third-party/boost third-party/boost EXCLUDE_FROM_ALL)

# Emulate the "find_package" target
add_library(Boost::boost INTERFACE IMPORTED)

file(GLOB BOOST_LIBS CONFIGURE_DEPENDS "${CMAKE_CURRENT_LIST_DIR}/../third-party/boost/libs/*")
foreach(lib_path ${BOOST_LIBS})
get_filename_component(lib_dir "${lib_path}" NAME)
if(TARGET "Boost::${lib_dir}")
target_link_libraries(Boost::boost INTERFACE "Boost::${lib_dir}")
endif()
endforeach()
endif()
5 changes: 4 additions & 1 deletion src/windows/CMakeLists.txt
Original file line numberDiff line numberDiff line change
Expand Up@@ -11,5 +11,8 @@ add_library(${MODULE} ${HEADER_LIST} ${SOURCE_LIST})
# Provide the includes together with this library
target_include_directories(${MODULE} PUBLIC include)

# Additional external libraries
include(Boost_DD)

# Link the additional libraries
target_link_libraries(${MODULE} PRIVATE libcommon)
target_link_libraries(${MODULE} PRIVATE libcommon Boost::boost setupapi)
16 changes: 16 additions & 0 deletions src/windows/include/displaydevice/windows/winapilayer.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,5 +16,21 @@ namespace display_device {
/** For details @see WinApiLayerInterface::query_display_config */
[[nodiscard]] std::optional<path_and_mode_data_t>
query_display_config(query_type_e type) const override;

/** For details @see WinApiLayerInterface::get_device_id */
[[nodiscard]] std::string
get_device_id(const DISPLAYCONFIG_PATH_INFO &path) const override;

/** For details @see WinApiLayerInterface::get_monitor_device_path */
[[nodiscard]] std::string
get_monitor_device_path(const DISPLAYCONFIG_PATH_INFO &path) const override;

/** For details @see WinApiLayerInterface::get_friendly_name */
[[nodiscard]] std::string
get_friendly_name(const DISPLAYCONFIG_PATH_INFO &path) const override;

/** For details @see WinApiLayerInterface::get_display_name */
[[nodiscard]] std::string
get_display_name(const DISPLAYCONFIG_PATH_INFO &path) const override;
};
} // namespace display_device
105 changes: 105 additions & 0 deletions src/windows/include/displaydevice/windows/winapilayerinterface.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -62,5 +62,110 @@ namespace display_device {
*/
[[nodiscard]] virtual std::optional<path_and_mode_data_t>
query_display_config(query_type_e type) const = 0;

/**
* @brief Get a stable and persistent device id for the path.
*
* This function tries to generate a unique id for the path that
* is persistent between driver re-installs and physical unplugging and
* replugging of the device.
*
* The best candidate for it could have been a "ContainerID" from the
* registry, however it was found to be unstable for the virtual display
* (probably because it uses the EDID for the id generation and the current
* virtual displays have incomplete EDID information). The "ContainerID"
* also does not change if the physical device is plugged into a different
* port and seems to be very stable, however because of virtual displays
* other solution was used.
*
* The accepted solution was to use the "InstanceID" and EDID (just to be
* on the safe side). "InstanceID" is semi-stable, it has some parts that
* change between driver re-installs and it has a part that changes based
* on the GPU port that the display is connected to. It is most likely to
* be unique, but since the MS documentation is lacking we are also hashing
* EDID information (contains serial ids, timestamps and etc. that should
* guarantee that identical displays are differentiated like with the
* "ContainerID"). Most importantly this information is stable for the virtual
* displays.
*
* After we remove the unstable parts from the "InstanceID" and hash everything
* together, we get an id that changes only when you connect the display to
* a different GPU port which seems to be acceptable.
*
* As a fallback we are using a hashed device path, in case the "InstanceID" or
* EDID is not available. At least if you don't do driver re-installs often
* and change the GPU ports, it will be stable for a while.
*
* @param path Path to get the device id for.
* @returns Device id, or an empty string if it could not be generated.
* @see query_display_config on how to get paths from the system.
*
* EXAMPLES:
* ```cpp
* DISPLAYCONFIG_PATH_INFO path;
* const WinApiLayerInterface* iface = getIface(...);
* const std::string device_path = iface->get_device_id(path);
* ```
*/
[[nodiscard]] virtual std::string
get_device_id(const DISPLAYCONFIG_PATH_INFO &path) const = 0;

/**
* @brief Get a string that represents a path from the adapter to the display target.
* @param path Path to get the string for.
* @returns String representation, or an empty string if it's not available.
* @see query_display_config on how to get paths from the system.
* @note In the rest of the code we refer to this string representation simply as the "device path".
* It is used as a simple way of grouping related path objects together and removing "bad" paths
* that don't have such string representation.
*
* EXAMPLES:
* ```cpp
* DISPLAYCONFIG_PATH_INFO path;
* const WinApiLayerInterface* iface = getIface(...);
* const std::string device_path = iface->get_monitor_device_path(path);
* ```
*/
[[nodiscard]] virtual std::string
get_monitor_device_path(const DISPLAYCONFIG_PATH_INFO &path) const = 0;

/**
* @brief Get the user friendly name for the path.
* @param path Path to get user friendly name for.
* @returns User friendly name for the path if available, empty string otherwise.
* @see query_display_config on how to get paths from the system.
* @note This is usually a monitor name (like "ROG PG279Q") and is most likely taken from EDID.
*
* EXAMPLES:
* ```cpp
* DISPLAYCONFIG_PATH_INFO path;
* const WinApiLayerInterface* iface = getIface(...);
* const std::string friendly_name = iface->get_friendly_name(path);
* ```
*/
[[nodiscard]] virtual std::string
get_friendly_name(const DISPLAYCONFIG_PATH_INFO &path) const = 0;

/**
* @brief Get the logical display name for the path.
*
* These are the "\\\\.\\DISPLAY1", "\\\\.\\DISPLAY2" and etc. display names that can
* change whenever Windows wants to change them.
*
* @param path Path to get user display name for.
* @returns Display name for the path if available, empty string otherwise.
* @see query_display_config on how to get paths from the system.
* @note Inactive paths can have these names already assigned to them, even
* though they are not even in use! There can also be duplicates.
*
* EXAMPLES:
* ```cpp
* DISPLAYCONFIG_PATH_INFO path;
* const WinApiLayerInterface* iface = getIface(...);
* const std::string display_name = iface->get_display_name(path);
* ```
*/
[[nodiscard]] virtual std::string
get_display_name(const DISPLAYCONFIG_PATH_INFO &path) const = 0;
};
} // namespace display_device
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
4 changes: 4 additions & 0 deletions .gitmodules
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
[submodule "third-party/boost"]
path = third-party/boost
url = https://github.com/boostorg/boost.git
branch = master
[submodule "third-party/googletest"]
path = third-party/googletest
url = https://github.com/google/googletest.git
Expand Down
3 changes: 3 additions & 0 deletions CMakeLists.txt
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,6 +15,9 @@ if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build." FORCE)
endif()

# Add our custom CMake modules to the global path
list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)

#
# Project optional configuration
#
Expand Down
28 changes: 28 additions & 0 deletions cmake/Boost_DD.cmake
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
#
# Loads the boost library giving the priority to the system package first, with a fallback
# to the submodule.
#
include_guard(GLOBAL)

find_package(Boost 1.85)
if(NOT Boost_FOUND)
message(STATUS "Boost v1.85.x package not found in system. Falling back to submodule.")

# Limit boost to the required libraries only
set(BOOST_INCLUDE_LIBRARIES
algorithm
scope
uuid)
add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/../third-party/boost third-party/boost EXCLUDE_FROM_ALL)

# Emulate the "find_package" target
add_library(Boost::boost INTERFACE IMPORTED)

file(GLOB BOOST_LIBS CONFIGURE_DEPENDS "${CMAKE_CURRENT_LIST_DIR}/../third-party/boost/libs/*")
foreach(lib_path ${BOOST_LIBS})
get_filename_component(lib_dir "${lib_path}" NAME)
if(TARGET "Boost::${lib_dir}")
target_link_libraries(Boost::boost INTERFACE "Boost::${lib_dir}")
endif()
endforeach()
endif()
5 changes: 4 additions & 1 deletion src/windows/CMakeLists.txt
Original file line numberDiff line numberDiff line change
Expand Up@@ -11,5 +11,8 @@ add_library(${MODULE} ${HEADER_LIST} ${SOURCE_LIST})
# Provide the includes together with this library
target_include_directories(${MODULE} PUBLIC include)

# Additional external libraries
include(Boost_DD)

# Link the additional libraries
target_link_libraries(${MODULE} PRIVATE libcommon)
target_link_libraries(${MODULE} PRIVATE libcommon Boost::boost setupapi)
16 changes: 16 additions & 0 deletions src/windows/include/displaydevice/windows/winapilayer.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,5 +16,21 @@ namespace display_device {
/** For details @see WinApiLayerInterface::query_display_config */
[[nodiscard]] std::optional<path_and_mode_data_t>
query_display_config(query_type_e type) const override;

/** For details @see WinApiLayerInterface::get_device_id */
[[nodiscard]] std::string
get_device_id(const DISPLAYCONFIG_PATH_INFO &path) const override;

/** For details @see WinApiLayerInterface::get_monitor_device_path */
[[nodiscard]] std::string
get_monitor_device_path(const DISPLAYCONFIG_PATH_INFO &path) const override;

/** For details @see WinApiLayerInterface::get_friendly_name */
[[nodiscard]] std::string
get_friendly_name(const DISPLAYCONFIG_PATH_INFO &path) const override;

/** For details @see WinApiLayerInterface::get_display_name */
[[nodiscard]] std::string
get_display_name(const DISPLAYCONFIG_PATH_INFO &path) const override;
};
} // namespace display_device
105 changes: 105 additions & 0 deletions src/windows/include/displaydevice/windows/winapilayerinterface.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -62,5 +62,110 @@ namespace display_device {
*/
[[nodiscard]] virtual std::optional<path_and_mode_data_t>
query_display_config(query_type_e type) const = 0;

/**
* @brief Get a stable and persistent device id for the path.
*
* This function tries to generate a unique id for the path that
* is persistent between driver re-installs and physical unplugging and
* replugging of the device.
*
* The best candidate for it could have been a "ContainerID" from the
* registry, however it was found to be unstable for the virtual display
* (probably because it uses the EDID for the id generation and the current
* virtual displays have incomplete EDID information). The "ContainerID"
* also does not change if the physical device is plugged into a different
* port and seems to be very stable, however because of virtual displays
* other solution was used.
*
* The accepted solution was to use the "InstanceID" and EDID (just to be
* on the safe side). "InstanceID" is semi-stable, it has some parts that
* change between driver re-installs and it has a part that changes based
* on the GPU port that the display is connected to. It is most likely to
* be unique, but since the MS documentation is lacking we are also hashing
* EDID information (contains serial ids, timestamps and etc. that should
* guarantee that identical displays are differentiated like with the
* "ContainerID"). Most importantly this information is stable for the virtual
* displays.
*
* After we remove the unstable parts from the "InstanceID" and hash everything
* together, we get an id that changes only when you connect the display to
* a different GPU port which seems to be acceptable.
*
* As a fallback we are using a hashed device path, in case the "InstanceID" or
* EDID is not available. At least if you don't do driver re-installs often
* and change the GPU ports, it will be stable for a while.
*
* @param path Path to get the device id for.
* @returns Device id, or an empty string if it could not be generated.
* @see query_display_config on how to get paths from the system.
*
* EXAMPLES:
* ```cpp
* DISPLAYCONFIG_PATH_INFO path;
* const WinApiLayerInterface* iface = getIface(...);
* const std::string device_path = iface->get_device_id(path);
* ```
*/
[[nodiscard]] virtual std::string
get_device_id(const DISPLAYCONFIG_PATH_INFO &path) const = 0;

/**
* @brief Get a string that represents a path from the adapter to the display target.
* @param path Path to get the string for.
* @returns String representation, or an empty string if it's not available.
* @see query_display_config on how to get paths from the system.
* @note In the rest of the code we refer to this string representation simply as the "device path".
* It is used as a simple way of grouping related path objects together and removing "bad" paths
* that don't have such string representation.
*
* EXAMPLES:
* ```cpp
* DISPLAYCONFIG_PATH_INFO path;
* const WinApiLayerInterface* iface = getIface(...);
* const std::string device_path = iface->get_monitor_device_path(path);
* ```
*/
[[nodiscard]] virtual std::string
get_monitor_device_path(const DISPLAYCONFIG_PATH_INFO &path) const = 0;

/**
* @brief Get the user friendly name for the path.
* @param path Path to get user friendly name for.
* @returns User friendly name for the path if available, empty string otherwise.
* @see query_display_config on how to get paths from the system.
* @note This is usually a monitor name (like "ROG PG279Q") and is most likely taken from EDID.
*
* EXAMPLES:
* ```cpp
* DISPLAYCONFIG_PATH_INFO path;
* const WinApiLayerInterface* iface = getIface(...);
* const std::string friendly_name = iface->get_friendly_name(path);
* ```
*/
[[nodiscard]] virtual std::string
get_friendly_name(const DISPLAYCONFIG_PATH_INFO &path) const = 0;

/**
* @brief Get the logical display name for the path.
*
* These are the "\\\\.\\DISPLAY1", "\\\\.\\DISPLAY2" and etc. display names that can
* change whenever Windows wants to change them.
*
* @param path Path to get user display name for.
* @returns Display name for the path if available, empty string otherwise.
* @see query_display_config on how to get paths from the system.
* @note Inactive paths can have these names already assigned to them, even
* though they are not even in use! There can also be duplicates.
*
* EXAMPLES:
* ```cpp
* DISPLAYCONFIG_PATH_INFO path;
* const WinApiLayerInterface* iface = getIface(...);
* const std::string display_name = iface->get_display_name(path);
* ```
*/
[[nodiscard]] virtual std::string
get_display_name(const DISPLAYCONFIG_PATH_INFO &path) const = 0;
};
} // namespace display_device
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
4 changes: 4 additions & 0 deletions .gitmodules
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
[submodule "third-party/boost"]
path = third-party/boost
url = https://github.com/boostorg/boost.git
branch = master
[submodule "third-party/googletest"]
path = third-party/googletest
url = https://github.com/google/googletest.git
Expand Down
3 changes: 3 additions & 0 deletions CMakeLists.txt
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,6 +15,9 @@ if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build." FORCE)
endif()

# Add our custom CMake modules to the global path
list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)

#
# Project optional configuration
#
Expand Down
28 changes: 28 additions & 0 deletions cmake/Boost_DD.cmake
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
#
# Loads the boost library giving the priority to the system package first, with a fallback
# to the submodule.
#
include_guard(GLOBAL)

find_package(Boost 1.85)
if(NOT Boost_FOUND)
message(STATUS "Boost v1.85.x package not found in system. Falling back to submodule.")

# Limit boost to the required libraries only
set(BOOST_INCLUDE_LIBRARIES
algorithm
scope
uuid)
add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/../third-party/boost third-party/boost EXCLUDE_FROM_ALL)

# Emulate the "find_package" target
add_library(Boost::boost INTERFACE IMPORTED)

file(GLOB BOOST_LIBS CONFIGURE_DEPENDS "${CMAKE_CURRENT_LIST_DIR}/../third-party/boost/libs/*")
foreach(lib_path ${BOOST_LIBS})
get_filename_component(lib_dir "${lib_path}" NAME)
if(TARGET "Boost::${lib_dir}")
target_link_libraries(Boost::boost INTERFACE "Boost::${lib_dir}")
endif()
endforeach()
endif()
5 changes: 4 additions & 1 deletion src/windows/CMakeLists.txt
Original file line numberDiff line numberDiff line change
Expand Up@@ -11,5 +11,8 @@ add_library(${MODULE} ${HEADER_LIST} ${SOURCE_LIST})
# Provide the includes together with this library
target_include_directories(${MODULE} PUBLIC include)

# Additional external libraries
include(Boost_DD)

# Link the additional libraries
target_link_libraries(${MODULE} PRIVATE libcommon)
target_link_libraries(${MODULE} PRIVATE libcommon Boost::boost setupapi)
16 changes: 16 additions & 0 deletions src/windows/include/displaydevice/windows/winapilayer.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,5 +16,21 @@ namespace display_device {
/** For details @see WinApiLayerInterface::query_display_config */
[[nodiscard]] std::optional<path_and_mode_data_t>
query_display_config(query_type_e type) const override;

/** For details @see WinApiLayerInterface::get_device_id */
[[nodiscard]] std::string
get_device_id(const DISPLAYCONFIG_PATH_INFO &path) const override;

/** For details @see WinApiLayerInterface::get_monitor_device_path */
[[nodiscard]] std::string
get_monitor_device_path(const DISPLAYCONFIG_PATH_INFO &path) const override;

/** For details @see WinApiLayerInterface::get_friendly_name */
[[nodiscard]] std::string
get_friendly_name(const DISPLAYCONFIG_PATH_INFO &path) const override;

/** For details @see WinApiLayerInterface::get_display_name */
[[nodiscard]] std::string
get_display_name(const DISPLAYCONFIG_PATH_INFO &path) const override;
};
} // namespace display_device
105 changes: 105 additions & 0 deletions src/windows/include/displaydevice/windows/winapilayerinterface.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -62,5 +62,110 @@ namespace display_device {
*/
[[nodiscard]] virtual std::optional<path_and_mode_data_t>
query_display_config(query_type_e type) const = 0;

/**
* @brief Get a stable and persistent device id for the path.
*
* This function tries to generate a unique id for the path that
* is persistent between driver re-installs and physical unplugging and
* replugging of the device.
*
* The best candidate for it could have been a "ContainerID" from the
* registry, however it was found to be unstable for the virtual display
* (probably because it uses the EDID for the id generation and the current
* virtual displays have incomplete EDID information). The "ContainerID"
* also does not change if the physical device is plugged into a different
* port and seems to be very stable, however because of virtual displays
* other solution was used.
*
* The accepted solution was to use the "InstanceID" and EDID (just to be
* on the safe side). "InstanceID" is semi-stable, it has some parts that
* change between driver re-installs and it has a part that changes based
* on the GPU port that the display is connected to. It is most likely to
* be unique, but since the MS documentation is lacking we are also hashing
* EDID information (contains serial ids, timestamps and etc. that should
* guarantee that identical displays are differentiated like with the
* "ContainerID"). Most importantly this information is stable for the virtual
* displays.
*
* After we remove the unstable parts from the "InstanceID" and hash everything
* together, we get an id that changes only when you connect the display to
* a different GPU port which seems to be acceptable.
*
* As a fallback we are using a hashed device path, in case the "InstanceID" or
* EDID is not available. At least if you don't do driver re-installs often
* and change the GPU ports, it will be stable for a while.
*
* @param path Path to get the device id for.
* @returns Device id, or an empty string if it could not be generated.
* @see query_display_config on how to get paths from the system.
*
* EXAMPLES:
* ```cpp
* DISPLAYCONFIG_PATH_INFO path;
* const WinApiLayerInterface* iface = getIface(...);
* const std::string device_path = iface->get_device_id(path);
* ```
*/
[[nodiscard]] virtual std::string
get_device_id(const DISPLAYCONFIG_PATH_INFO &path) const = 0;

/**
* @brief Get a string that represents a path from the adapter to the display target.
* @param path Path to get the string for.
* @returns String representation, or an empty string if it's not available.
* @see query_display_config on how to get paths from the system.
* @note In the rest of the code we refer to this string representation simply as the "device path".
* It is used as a simple way of grouping related path objects together and removing "bad" paths
* that don't have such string representation.
*
* EXAMPLES:
* ```cpp
* DISPLAYCONFIG_PATH_INFO path;
* const WinApiLayerInterface* iface = getIface(...);
* const std::string device_path = iface->get_monitor_device_path(path);
* ```
*/
[[nodiscard]] virtual std::string
get_monitor_device_path(const DISPLAYCONFIG_PATH_INFO &path) const = 0;

/**
* @brief Get the user friendly name for the path.
* @param path Path to get user friendly name for.
* @returns User friendly name for the path if available, empty string otherwise.
* @see query_display_config on how to get paths from the system.
* @note This is usually a monitor name (like "ROG PG279Q") and is most likely taken from EDID.
*
* EXAMPLES:
* ```cpp
* DISPLAYCONFIG_PATH_INFO path;
* const WinApiLayerInterface* iface = getIface(...);
* const std::string friendly_name = iface->get_friendly_name(path);
* ```
*/
[[nodiscard]] virtual std::string
get_friendly_name(const DISPLAYCONFIG_PATH_INFO &path) const = 0;

/**
* @brief Get the logical display name for the path.
*
* These are the "\\\\.\\DISPLAY1", "\\\\.\\DISPLAY2" and etc. display names that can
* change whenever Windows wants to change them.
*
* @param path Path to get user display name for.
* @returns Display name for the path if available, empty string otherwise.
* @see query_display_config on how to get paths from the system.
* @note Inactive paths can have these names already assigned to them, even
* though they are not even in use! There can also be duplicates.
*
* EXAMPLES:
* ```cpp
* DISPLAYCONFIG_PATH_INFO path;
* const WinApiLayerInterface* iface = getIface(...);
* const std::string display_name = iface->get_display_name(path);
* ```
*/
[[nodiscard]] virtual std::string
get_display_name(const DISPLAYCONFIG_PATH_INFO &path) const = 0;
};
} // namespace display_device
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
4 changes: 4 additions & 0 deletions .gitmodules
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
[submodule "third-party/boost"]
path = third-party/boost
url = https://github.com/boostorg/boost.git
branch = master
[submodule "third-party/googletest"]
path = third-party/googletest
url = https://github.com/google/googletest.git
Expand Down
3 changes: 3 additions & 0 deletions CMakeLists.txt
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,6 +15,9 @@ if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build." FORCE)
endif()

# Add our custom CMake modules to the global path
list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)

#
# Project optional configuration
#
Expand Down
28 changes: 28 additions & 0 deletions cmake/Boost_DD.cmake
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
#
# Loads the boost library giving the priority to the system package first, with a fallback
# to the submodule.
#
include_guard(GLOBAL)

find_package(Boost 1.85)
if(NOT Boost_FOUND)
message(STATUS "Boost v1.85.x package not found in system. Falling back to submodule.")

# Limit boost to the required libraries only
set(BOOST_INCLUDE_LIBRARIES
algorithm
scope
uuid)
add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/../third-party/boost third-party/boost EXCLUDE_FROM_ALL)

# Emulate the "find_package" target
add_library(Boost::boost INTERFACE IMPORTED)

file(GLOB BOOST_LIBS CONFIGURE_DEPENDS "${CMAKE_CURRENT_LIST_DIR}/../third-party/boost/libs/*")
foreach(lib_path ${BOOST_LIBS})
get_filename_component(lib_dir "${lib_path}" NAME)
if(TARGET "Boost::${lib_dir}")
target_link_libraries(Boost::boost INTERFACE "Boost::${lib_dir}")
endif()
endforeach()
endif()
5 changes: 4 additions & 1 deletion src/windows/CMakeLists.txt
Original file line numberDiff line numberDiff line change
Expand Up@@ -11,5 +11,8 @@ add_library(${MODULE} ${HEADER_LIST} ${SOURCE_LIST})
# Provide the includes together with this library
target_include_directories(${MODULE} PUBLIC include)

# Additional external libraries
include(Boost_DD)

# Link the additional libraries
target_link_libraries(${MODULE} PRIVATE libcommon)
target_link_libraries(${MODULE} PRIVATE libcommon Boost::boost setupapi)
16 changes: 16 additions & 0 deletions src/windows/include/displaydevice/windows/winapilayer.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,5 +16,21 @@ namespace display_device {
/** For details @see WinApiLayerInterface::query_display_config */
[[nodiscard]] std::optional<path_and_mode_data_t>
query_display_config(query_type_e type) const override;

/** For details @see WinApiLayerInterface::get_device_id */
[[nodiscard]] std::string
get_device_id(const DISPLAYCONFIG_PATH_INFO &path) const override;

/** For details @see WinApiLayerInterface::get_monitor_device_path */
[[nodiscard]] std::string
get_monitor_device_path(const DISPLAYCONFIG_PATH_INFO &path) const override;

/** For details @see WinApiLayerInterface::get_friendly_name */
[[nodiscard]] std::string
get_friendly_name(const DISPLAYCONFIG_PATH_INFO &path) const override;

/** For details @see WinApiLayerInterface::get_display_name */
[[nodiscard]] std::string
get_display_name(const DISPLAYCONFIG_PATH_INFO &path) const override;
};
} // namespace display_device
105 changes: 105 additions & 0 deletions src/windows/include/displaydevice/windows/winapilayerinterface.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -62,5 +62,110 @@ namespace display_device {
*/
[[nodiscard]] virtual std::optional<path_and_mode_data_t>
query_display_config(query_type_e type) const = 0;

/**
* @brief Get a stable and persistent device id for the path.
*
* This function tries to generate a unique id for the path that
* is persistent between driver re-installs and physical unplugging and
* replugging of the device.
*
* The best candidate for it could have been a "ContainerID" from the
* registry, however it was found to be unstable for the virtual display
* (probably because it uses the EDID for the id generation and the current
* virtual displays have incomplete EDID information). The "ContainerID"
* also does not change if the physical device is plugged into a different
* port and seems to be very stable, however because of virtual displays
* other solution was used.
*
* The accepted solution was to use the "InstanceID" and EDID (just to be
* on the safe side). "InstanceID" is semi-stable, it has some parts that
* change between driver re-installs and it has a part that changes based
* on the GPU port that the display is connected to. It is most likely to
* be unique, but since the MS documentation is lacking we are also hashing
* EDID information (contains serial ids, timestamps and etc. that should
* guarantee that identical displays are differentiated like with the
* "ContainerID"). Most importantly this information is stable for the virtual
* displays.
*
* After we remove the unstable parts from the "InstanceID" and hash everything
* together, we get an id that changes only when you connect the display to
* a different GPU port which seems to be acceptable.
*
* As a fallback we are using a hashed device path, in case the "InstanceID" or
* EDID is not available. At least if you don't do driver re-installs often
* and change the GPU ports, it will be stable for a while.
*
* @param path Path to get the device id for.
* @returns Device id, or an empty string if it could not be generated.
* @see query_display_config on how to get paths from the system.
*
* EXAMPLES:
* ```cpp
* DISPLAYCONFIG_PATH_INFO path;
* const WinApiLayerInterface* iface = getIface(...);
* const std::string device_path = iface->get_device_id(path);
* ```
*/
[[nodiscard]] virtual std::string
get_device_id(const DISPLAYCONFIG_PATH_INFO &path) const = 0;

/**
* @brief Get a string that represents a path from the adapter to the display target.
* @param path Path to get the string for.
* @returns String representation, or an empty string if it's not available.
* @see query_display_config on how to get paths from the system.
* @note In the rest of the code we refer to this string representation simply as the "device path".
* It is used as a simple way of grouping related path objects together and removing "bad" paths
* that don't have such string representation.
*
* EXAMPLES:
* ```cpp
* DISPLAYCONFIG_PATH_INFO path;
* const WinApiLayerInterface* iface = getIface(...);
* const std::string device_path = iface->get_monitor_device_path(path);
* ```
*/
[[nodiscard]] virtual std::string
get_monitor_device_path(const DISPLAYCONFIG_PATH_INFO &path) const = 0;

/**
* @brief Get the user friendly name for the path.
* @param path Path to get user friendly name for.
* @returns User friendly name for the path if available, empty string otherwise.
* @see query_display_config on how to get paths from the system.
* @note This is usually a monitor name (like "ROG PG279Q") and is most likely taken from EDID.
*
* EXAMPLES:
* ```cpp
* DISPLAYCONFIG_PATH_INFO path;
* const WinApiLayerInterface* iface = getIface(...);
* const std::string friendly_name = iface->get_friendly_name(path);
* ```
*/
[[nodiscard]] virtual std::string
get_friendly_name(const DISPLAYCONFIG_PATH_INFO &path) const = 0;

/**
* @brief Get the logical display name for the path.
*
* These are the "\\\\.\\DISPLAY1", "\\\\.\\DISPLAY2" and etc. display names that can
* change whenever Windows wants to change them.
*
* @param path Path to get user display name for.
* @returns Display name for the path if available, empty string otherwise.
* @see query_display_config on how to get paths from the system.
* @note Inactive paths can have these names already assigned to them, even
* though they are not even in use! There can also be duplicates.
*
* EXAMPLES:
* ```cpp
* DISPLAYCONFIG_PATH_INFO path;
* const WinApiLayerInterface* iface = getIface(...);
* const std::string display_name = iface->get_display_name(path);
* ```
*/
[[nodiscard]] virtual std::string
get_display_name(const DISPLAYCONFIG_PATH_INFO &path) const = 0;
};
} // namespace display_device
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
4 changes: 4 additions & 0 deletions .gitmodules
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
[submodule "third-party/boost"]
path = third-party/boost
url = https://github.com/boostorg/boost.git
branch = master
[submodule "third-party/googletest"]
path = third-party/googletest
url = https://github.com/google/googletest.git
Expand Down
3 changes: 3 additions & 0 deletions CMakeLists.txt
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,6 +15,9 @@ if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build." FORCE)
endif()

# Add our custom CMake modules to the global path
list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)

#
# Project optional configuration
#
Expand Down
28 changes: 28 additions & 0 deletions cmake/Boost_DD.cmake
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
#
# Loads the boost library giving the priority to the system package first, with a fallback
# to the submodule.
#
include_guard(GLOBAL)

find_package(Boost 1.85)
if(NOT Boost_FOUND)
message(STATUS "Boost v1.85.x package not found in system. Falling back to submodule.")

# Limit boost to the required libraries only
set(BOOST_INCLUDE_LIBRARIES
algorithm
scope
uuid)
add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/../third-party/boost third-party/boost EXCLUDE_FROM_ALL)

# Emulate the "find_package" target
add_library(Boost::boost INTERFACE IMPORTED)

file(GLOB BOOST_LIBS CONFIGURE_DEPENDS "${CMAKE_CURRENT_LIST_DIR}/../third-party/boost/libs/*")
foreach(lib_path ${BOOST_LIBS})
get_filename_component(lib_dir "${lib_path}" NAME)
if(TARGET "Boost::${lib_dir}")
target_link_libraries(Boost::boost INTERFACE "Boost::${lib_dir}")
endif()
endforeach()
endif()
5 changes: 4 additions & 1 deletion src/windows/CMakeLists.txt
Original file line numberDiff line numberDiff line change
Expand Up@@ -11,5 +11,8 @@ add_library(${MODULE} ${HEADER_LIST} ${SOURCE_LIST})
# Provide the includes together with this library
target_include_directories(${MODULE} PUBLIC include)

# Additional external libraries
include(Boost_DD)

# Link the additional libraries
target_link_libraries(${MODULE} PRIVATE libcommon)
target_link_libraries(${MODULE} PRIVATE libcommon Boost::boost setupapi)
16 changes: 16 additions & 0 deletions src/windows/include/displaydevice/windows/winapilayer.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,5 +16,21 @@ namespace display_device {
/** For details @see WinApiLayerInterface::query_display_config */
[[nodiscard]] std::optional<path_and_mode_data_t>
query_display_config(query_type_e type) const override;

/** For details @see WinApiLayerInterface::get_device_id */
[[nodiscard]] std::string
get_device_id(const DISPLAYCONFIG_PATH_INFO &path) const override;

/** For details @see WinApiLayerInterface::get_monitor_device_path */
[[nodiscard]] std::string
get_monitor_device_path(const DISPLAYCONFIG_PATH_INFO &path) const override;

/** For details @see WinApiLayerInterface::get_friendly_name */
[[nodiscard]] std::string
get_friendly_name(const DISPLAYCONFIG_PATH_INFO &path) const override;

/** For details @see WinApiLayerInterface::get_display_name */
[[nodiscard]] std::string
get_display_name(const DISPLAYCONFIG_PATH_INFO &path) const override;
};
} // namespace display_device
105 changes: 105 additions & 0 deletions src/windows/include/displaydevice/windows/winapilayerinterface.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -62,5 +62,110 @@ namespace display_device {
*/
[[nodiscard]] virtual std::optional<path_and_mode_data_t>
query_display_config(query_type_e type) const = 0;

/**
* @brief Get a stable and persistent device id for the path.
*
* This function tries to generate a unique id for the path that
* is persistent between driver re-installs and physical unplugging and
* replugging of the device.
*
* The best candidate for it could have been a "ContainerID" from the
* registry, however it was found to be unstable for the virtual display
* (probably because it uses the EDID for the id generation and the current
* virtual displays have incomplete EDID information). The "ContainerID"
* also does not change if the physical device is plugged into a different
* port and seems to be very stable, however because of virtual displays
* other solution was used.
*
* The accepted solution was to use the "InstanceID" and EDID (just to be
* on the safe side). "InstanceID" is semi-stable, it has some parts that
* change between driver re-installs and it has a part that changes based
* on the GPU port that the display is connected to. It is most likely to
* be unique, but since the MS documentation is lacking we are also hashing
* EDID information (contains serial ids, timestamps and etc. that should
* guarantee that identical displays are differentiated like with the
* "ContainerID"). Most importantly this information is stable for the virtual
* displays.
*
* After we remove the unstable parts from the "InstanceID" and hash everything
* together, we get an id that changes only when you connect the display to
* a different GPU port which seems to be acceptable.
*
* As a fallback we are using a hashed device path, in case the "InstanceID" or
* EDID is not available. At least if you don't do driver re-installs often
* and change the GPU ports, it will be stable for a while.
*
* @param path Path to get the device id for.
* @returns Device id, or an empty string if it could not be generated.
* @see query_display_config on how to get paths from the system.
*
* EXAMPLES:
* ```cpp
* DISPLAYCONFIG_PATH_INFO path;
* const WinApiLayerInterface* iface = getIface(...);
* const std::string device_path = iface->get_device_id(path);
* ```
*/
[[nodiscard]] virtual std::string
get_device_id(const DISPLAYCONFIG_PATH_INFO &path) const = 0;

/**
* @brief Get a string that represents a path from the adapter to the display target.
* @param path Path to get the string for.
* @returns String representation, or an empty string if it's not available.
* @see query_display_config on how to get paths from the system.
* @note In the rest of the code we refer to this string representation simply as the "device path".
* It is used as a simple way of grouping related path objects together and removing "bad" paths
* that don't have such string representation.
*
* EXAMPLES:
* ```cpp
* DISPLAYCONFIG_PATH_INFO path;
* const WinApiLayerInterface* iface = getIface(...);
* const std::string device_path = iface->get_monitor_device_path(path);
* ```
*/
[[nodiscard]] virtual std::string
get_monitor_device_path(const DISPLAYCONFIG_PATH_INFO &path) const = 0;

/**
* @brief Get the user friendly name for the path.
* @param path Path to get user friendly name for.
* @returns User friendly name for the path if available, empty string otherwise.
* @see query_display_config on how to get paths from the system.
* @note This is usually a monitor name (like "ROG PG279Q") and is most likely taken from EDID.
*
* EXAMPLES:
* ```cpp
* DISPLAYCONFIG_PATH_INFO path;
* const WinApiLayerInterface* iface = getIface(...);
* const std::string friendly_name = iface->get_friendly_name(path);
* ```
*/
[[nodiscard]] virtual std::string
get_friendly_name(const DISPLAYCONFIG_PATH_INFO &path) const = 0;

/**
* @brief Get the logical display name for the path.
*
* These are the "\\\\.\\DISPLAY1", "\\\\.\\DISPLAY2" and etc. display names that can
* change whenever Windows wants to change them.
*
* @param path Path to get user display name for.
* @returns Display name for the path if available, empty string otherwise.
* @see query_display_config on how to get paths from the system.
* @note Inactive paths can have these names already assigned to them, even
* though they are not even in use! There can also be duplicates.
*
* EXAMPLES:
* ```cpp
* DISPLAYCONFIG_PATH_INFO path;
* const WinApiLayerInterface* iface = getIface(...);
* const std::string display_name = iface->get_display_name(path);
* ```
*/
[[nodiscard]] virtual std::string
get_display_name(const DISPLAYCONFIG_PATH_INFO &path) const = 0;
};
} // namespace display_device
Loading