structures/Display/Display.psm1
# A display source with corresponding display target to use if the source is enabled. Similar to a display "path" except with an optional target. class Display { # Display source (eg graphics card output) [Source]$Source # Display target (eg monitor)- note target data is only available if the source is enabled [Target]$Target # See DisplayTypes.ps1xml for calculated properties Display ( [uint32]$sourceId, [DisplayDevices+DisplayDevice]$sourceDevice) { $this.Source = [Source]::new($sourceId, $sourceDevice) } Display ( [uint32]$sourceId, [DisplayDevices+DisplayDevice]$sourceDevice, [DisplayConfig+DisplayConfigPathTargetInfo]$pathTargetInfo) { $this.Source = [Source]::new($sourceId, $sourceDevice) $this.Target = [Target]::new($pathTargetInfo) } [bool]Equals($display) { return $this.Id -eq $display.Id } [string]ToJsonString() { return "`n" + ($this | ConvertTo-Json | Out-String).Trim() } [string]ToTableString() { return "`n" + ($this | Format-Table | Out-String).Trim() } [bool]Disable() { return $this._SetEnablement($false, $null) } [bool]Enable() { return $this._SetEnablement($true, $null) } [bool]Enable($destinationTargetId) { return $this._SetEnablement($true, $destinationTargetId) } [bool]SetResolution($width, $height, $refreshRate) { return $this.Source._SetResolution($width, $height, $refreshRate) } [bool]SetResolution($width, $height) { return $this.SetResolution($width, $height, $null) } [bool]SetToRecommendedResolution() { $recommendedResolution = $this.Target._GetRecommendedResolution() if (-not $recommendedResolution) { return $false } return $this.SetResolution($recommendedResolution.Width, $recommendedResolution.Height) } [bool]EnableHdr() { if (-not $this.Target) { return $true } return $this.Target._SetHdrEnablement($true) } [bool]DisableHdr() { if (-not $this.Target) { return $true } return $this.Target._SetHdrEnablement($false) } [bool]_GetIsEnabled() { $configInfo = $this._GetDisplayConfigInfo($false) if ($null -eq $configInfo) { return $null } foreach ($path in $configInfo.Paths) { # Paths should always be keyed on source and target id, so we can safely return the first match based on those ids if (($path.sourceInfo.id -eq $this.Source.Id) -and ($path.targetInfo.id -eq $this.Target.Id)) { # Since we didn't fetch inactive paths from display config, all returned paths should be active. Check it anyways to be safe. return $path.flags.HasFlag([DisplayConfig+DisplayConfigPathInfoFlags]::PathActive) } } # If the display isn't enabled, there won't be an active path for it return $false } hidden [bool]_SetEnablement($enablement, $destinationTargetId) { $actualDestinationTargetId = $null if ($enablement) { if ($destinationTargetId) { $actualDestinationTargetId = $destinationTargetId } elseif ($this.Target) { $actualDestinationTargetId = $this.Target.Id } else { Write-PSFMessage -Level Debug -Message "Display $($this.Description) cannot be enabled since it does not have a target stored from when it was created and one wasn't supplied" return $true } } elseif ($this.Primary) { Write-PSFMessage -Level Debug -Message "Display $($this.Description) cannot be disabled since it is the primary display" return $true } # To enable a display, we need paths that aren't active in order to activate one. To disable one, it is ok to get only active paths. $configInfo = $this._GetDisplayConfigInfo($enablement) if ($null -eq $configInfo) { return $false } for ($pathIndex = 0; $pathIndex -lt $configInfo.Paths.Length; $pathIndex++) { $path = $configInfo.Paths[$pathIndex] if ($path.sourceInfo.id -ne $this.Source.Id) { continue } if ($enablement -and $path.targetInfo.id -ne $actualDestinationTargetId) { continue } if ($enablement -and -not $path.targetInfo.targetAvailable) { continue } if ($enablement -eq $path.flags.HasFlag([DisplayConfig+DisplayConfigPathInfoFlags]::PathActive)) { Write-PSFMessage -Level Debug -Message "Path for Display $($this.Description) already has enablement state of $enablement, nothing to change" return $true } # Update the path active flag, validate and set enablement. if ($enablement) { $path.flags = $path.flags -bor [DisplayConfig+DisplayConfigPathInfoFlags]::PathActive } else { $path.flags = $path.flags -band (-bnot [DisplayConfig+DisplayConfigPathInfoFlags]::PathActive) } $configInfo.Paths[$pathIndex] = $path $validateSetDisplayConfigFlags = [DisplayConfig+SetDisplayConfigFlags]::Validate -bor [DisplayConfig+SetDisplayConfigFlags]::UseSuppliedDisplayConfig $validateSetDisplayConfigResult = [DisplayConfig]::SetDisplayConfig($configInfo.PathsCount, $configInfo.Paths, $configInfo.ModesCount, $configInfo.Modes, $validateSetDisplayConfigFlags) if ($validateSetDisplayConfigResult -ne [Win32Error]::ERROR_SUCCESS) { Write-PSFMessage -Level Critical -Message "Error validating the setting of display $($this.Description) enablement state to $enablement (error code $([Win32Error]$validateSetDisplayConfigResult))" return $false } $setDisplayConfigFlags = [DisplayConfig+SetDisplayConfigFlags]::Apply -bor [DisplayConfig+SetDisplayConfigFlags]::UseSuppliedDisplayConfig ` -bor [DisplayConfig+SetDisplayConfigFlags]::AllowChanges -bor [DisplayConfig+SetDisplayConfigFlags]::SaveToDatabase $setDisplayConfigResult = [DisplayConfig]::SetDisplayConfig($configInfo.PathsCount, $configInfo.Paths, $configInfo.ModesCount, $configInfo.Modes, $setDisplayConfigFlags) if ($setDisplayConfigResult -ne [Win32Error]::ERROR_SUCCESS) { Write-PSFMessage -Level Critical -Message "Error setting display $($this.Description) enablement state to $enablement (error code $([Win32Error]$setDisplayConfigResult))" return $false } Write-PSFMessage -Level Verbose -Message "Display $($this.Description) enablement state set to $enablement successfully" return $true } Write-PSFMessage -Level Debug -Message "No display configuration path found for display $($this.Description) when setting enablement to $($enablement): this likely indicates $(if ($enablement) { "the destination target id $actualDestinationTargetId doesn't exist- maybe refresh monitor settings" } else { "the display is already disabled" })" return $true } hidden [PSCustomObject]_GetDisplayConfigInfo([bool]$includeInactivePaths) { $pathsCount = 0; $modesCount = 0; $queryDisplayConfigFlags = if ($includeInactivePaths) { [DisplayConfig+QueryDisplayConfigFlags]::AllPaths } else { [DisplayConfig+QueryDisplayConfigFlags]::OnlyActivePaths } $displayConfigBufferSizesResult = [DisplayConfig]::GetDisplayConfigBufferSizes($queryDisplayConfigFlags, [ref]$pathsCount, [ref]$modesCount); if ($displayConfigBufferSizesResult -ne [Win32Error]::ERROR_SUCCESS) { Write-PSFMessage -Level Critical -Message "Failed to get display configuration buffer sizes for display $($this.Description) (error code $([Win32Error]$displayConfigBufferSizesResult))" return $null } $paths = @() $modes = @() $displayConfigResult = [DisplayConfig]::QueryDisplayConfig($queryDisplayConfigFlags, [ref]$pathsCount, [ref]$paths, [ref]$modesCount, [ref]$modes); if ($displayConfigResult -ne [Win32Error]::ERROR_SUCCESS) { Write-PSFMessage -Level Critical -Message "Failed to get display configuration path for display $($this.Description) (error code $([Win32Error]$displayConfigResult))" return $null } return @{ PathsCount = $pathsCount Paths = $paths ModesCount = $modesCount Modes = $modes } } } # A single potential means of graphics output class Source { # Unique source identifier, typically an index (eg 0) [uint32]$Id # Source name (eg "\\.\DISPLAY1") [string]$Name # Source description, usually graphics card name (eg "Intel(R) HD Graphics Family") [string]$Description Source ( [uint32]$sourceId, [DisplayDevices+DisplayDevice]$sourceDevice) { $this.Id = $sourceId $this.Name = $sourceDevice.DeviceName $this.Description = $sourceDevice.DeviceString } # Per MSDN: "whether a monitor is presented as being "on" by the respective GDI view" hidden [bool]_GetIsActive() { $displayDevice = $this._GetDisplayDevice() if ($null -eq $displayDevice) { return $false } return $displayDevice.StateFlags.HasFlag([DisplayDevices+DisplayDeviceStateFlags]::DeviceActive) } # Source is primary (only one display can have this) hidden [bool]_GetIsPrimary() { $displayDevice = $this._GetDisplayDevice() if ($null -eq $displayDevice) { return $false } return $displayDevice.StateFlags.HasFlag([DisplayDevices+DisplayDeviceStateFlags]::PrimaryDevice) } # Configured output resolution and refresh rate- note return value is actually Resolution but PsCustomObject makes it nullable hidden [PsCustomObject]_GetResolution() { if (-not $this._GetIsActive()) { return $null } $deviceMode = $this._GetDisplaySettingsDeviceMode() if ($null -eq $deviceMode) { return $null } return [Resolution]::new($deviceMode.dmPelsWidth, $deviceMode.dmPelsHeight, $deviceMode.dmDisplayFrequency) } # (x, y) Position in a multi monitor setup hidden [PsCustomObject]_GetPosition() { if (-not $this._GetIsActive()) { return $null } $deviceMode = $this._GetDisplaySettingsDeviceMode() if ($null -eq $deviceMode) { return $null } return [Position]::new($deviceMode.dmPositionX, $deviceMode.dmPositionY) } hidden [bool]_SetResolution($width, $height, $refreshRate) { if (-not $this._GetIsActive()) { Write-PSFMessage -Level Debug -Message "Cannot set resolution of disabled display source $($this.Name)" return $true } $deviceMode = $this._GetDisplaySettingsDeviceMode() if ($null -eq $deviceMode) { return $false } $refreshRateTolerance = 3 # Tolerance for when to consider a refresh rate close enough to not need changing $refreshRateWithinTolerance = -not $refreshRate -or ([Math]::Abs($deviceMode.dmDisplayFrequency - $refreshRate) -le $refreshRateTolerance) if ($deviceMode.dmPelsWidth -eq $width -and $deviceMode.dmPelsHeight -eq $height -and $refreshRateWithinTolerance) { Write-PSFMessage -Level Debug -Message "Current resolution for source $($this.Name) of $($deviceMode.dmPelsWidth)x$($deviceMode.dmPelsHeight)x$($deviceMode.dmDisplayFrequency) is equivalent to requested resolution- nothing to change" return $true } $requestedResolutionStr = "$($width)x$($height)$(if($refreshRate){"@$($refreshRate)fps"})" $deviceMode.dmPelsWidth = $width $deviceMode.dmPelsHeight = $height if ($refreshRate) { $deviceMode.dmDisplayFrequency = $refreshRate } $validateChangeDisplaySettingsResult = [DisplaySettings]::ChangeDisplaySettingsEx($this.Name, [ref]$deviceMode, [DisplaySettings+ChangeDisplaySettingsFlags]::Test) if ($validateChangeDisplaySettingsResult -ne [DisplaySettings+ChangeDisplaySettingsResult]::DISP_CHANGE_SUCCESSFUL) { Write-PSFMessage -Level Warning -Message "Failed to validate source $($this.Name) display settings could be changed to $requestedResolutionStr (error code $([DisplaySettings+ChangeDisplaySettingsResult]$validateChangeDisplaySettingsResult))" return $false } $changeDisplaySettingsResult = [DisplaySettings]::ChangeDisplaySettingsEx($this.Name, [ref]$deviceMode, [DisplaySettings+ChangeDisplaySettingsFlags]::UpdateRegistry) if ($changeDisplaySettingsResult -ne [DisplaySettings+ChangeDisplaySettingsResult]::DISP_CHANGE_SUCCESSFUL) { Write-PSFMessage -Level Critical -Message "Failed to change resolution to $requestedResolutionStr for source $($this.Name) (error code $([DisplaySettings+ChangeDisplaySettingsResult]$changeDisplaySettingsResult))" return $false } Write-PSFMessage -Level Verbose -Message "Source $($this.Name) resolution successfully set to $requestedResolutionStr" return $true } # Return value is in structure DisplaySettings+DevMode, use PSCustomObject to make it nullable hidden [PSCustomObject]_GetDisplaySettingsDeviceMode() { $deviceMode = New-Object DisplaySettings+DevMode $deviceMode.dmSize = [System.Runtime.InteropServices.Marshal]::SizeOf($deviceMode) $enumDisplaySettingsMode = [DisplaySettings+EnumDisplaySettingsMode]::CurrentSettings $enumDisplaySettingsFlags = [DisplaySettings+EnumDisplaySettingsFlags]::RotatedMode $enumDisplaySettingsResult = [DisplaySettings]::EnumDisplaySettingsEx($this.Name, $enumDisplaySettingsMode, [ref]$deviceMode, $enumDisplaySettingsFlags) if (-not $enumDisplaySettingsResult) { Write-PSFMessage -Level Critical -Message "Failed to get device mode for source $($this.Name)" return $null } return $deviceMode } # Return value is in structure DisplayDevices+DisplayDevice, use PSCustomObject to make it nullable hidden [PSCustomObject]_GetDisplayDevice() { $displayDevice = New-Object DisplayDevices+DisplayDevice $displayDevice.cb = [System.Runtime.InteropServices.Marshal]::SizeOf($displayDevice) $enumDisplayDevicesResult = [DisplayDevices]::EnumDisplayDevices([NullString]::Value, $this.Id, [ref]$displayDevice, [DisplayDevices+EnumDisplayDevicesFlags]::None) if (-not $enumDisplayDevicesResult) { Write-PSFMessage -Level Critical -Message "Failed to get display device for source $($this.Name) (error code $([Win32Error]$enumDisplayDevicesResult))" return $null } return $displayDevice } } # The connection and/or display to which the source is outputting class Target { # Unique target identifiers [uint32]$Id # TODO the target and source adapter ids seem to always be the same, even though we only need the target one. Should this just live at the Display level? [DisplayConfig+LUID]$AdapterId # Friendly target name similar to what a user would see in Windows settings: # From EDID if available, else generic based on output technology, else "Unknown" [string]$FriendlyName # The type of output technology being used eg HDMI or DisplayPort [string]$ConnectionType Target ([DisplayConfig+DisplayConfigPathTargetInfo]$pathTargetInfo) { # TODO require the below to be valid $this.Id = $pathTargetInfo.id $this.AdapterId = $pathTargetInfo.adapterId # Side load friendly name from DisplayConfigGetDeviceInfo at object build time- it's only informational. $targetName = New-Object DisplayConfig+DisplayConfigTargetDeviceName $targetNameHeader = New-Object DisplayConfig+DisplayConfigDeviceInfoHeader $targetNameHeader.id = $this.Id $targetNameHeader.adapterId = $this.AdapterId $targetNameHeader.size = [uint32]([System.Runtime.InteropServices.Marshal]::SizeOf($targetName)) $targetNameHeader.type = [DisplayConfig+DisplayConfigDeviceInfoType]::DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME $targetName.header = $targetNameHeader $targetNameResult = [DisplayConfig]::DisplayConfigGetDeviceInfo([ref]$targetName) $this.FriendlyName = "Unknown" $this.ConnectionType = [string]$pathTargetInfo.outputTechnology if ($targetNameResult -eq [Win32Error]::ERROR_SUCCESS) { if ($targetName.flags.value.HasFlag([DisplayConfig+DisplayConfigTargetDeviceNameFlagValue]::FRIENDLY_NAME_FROM_EDID)) { $this.FriendlyName = $targetName.monitorFriendlyDeviceName } } else { Write-PSFMessage -Level Warning -Message "Error fetching friendly name for target id $($this.Id) (error code $([Win32Error]$targetNameResult))" } if ($this.FriendlyName -eq "Unknown") { if (-not ([string]::IsNullOrWhitespace($pathTargetInfo.outputTechnology))) { $this.FriendlyName = [string]$pathTargetInfo.outputTechnology + " Display" } else { Write-PSFMessage -Level Debug -Message "Unable to find friendly name from fetched target information for target id $($this.Id)" } } } # HDR support and enablement info hidden [HdrInfo]_GetHdrInfo() { $advancedColorInfo = New-Object DisplayConfig+DisplayConfigGetAdvancedColorInfo $advancedColorInfoHeader = New-Object DisplayConfig+DisplayConfigDeviceInfoHeader $advancedColorInfoHeader.id = $this.Id $advancedColorInfoHeader.adapterId = $this.AdapterId $advancedColorInfoHeader.size = [uint32]([System.Runtime.InteropServices.Marshal]::SizeOf($advancedColorInfo)); $advancedColorInfoHeader.type = [DisplayConfig+DisplayConfigDeviceInfoType]::DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO; $advancedColorInfo.header = $advancedColorInfoHeader $advancedColorInfoResult = [DisplayConfig]::DisplayConfigGetDeviceInfo([ref]$advancedColorInfo); if ($advancedColorInfoResult -ne [Win32Error]::ERROR_SUCCESS) { Write-PSFMessage -Level Critical -Message "Failed to get HDR info for target $($this.FriendlyName) (error code $([Win32Error]$advancedColorInfoResult))" return $null } $hdrSupported = $advancedColorInfo.values.HasFlag([DisplayConfig+DisplayConfigGetAdvancedColorInfoValues]::AdvancedColorSupported) $hdrEnabled = $advancedColorInfo.values.HasFlag([DisplayConfig+DisplayConfigGetAdvancedColorInfoValues]::AdvancedColorEnabled) return [HdrInfo]::new($hdrSupported, $hdrEnabled, $advancedColorInfo.bitsPerColorChannel) } # Recommended target resolution per EDID (note recommended refresh rate is not available) hidden [Resolution]_GetRecommendedResolution() { $targetPreferredMode = New-Object DisplayConfig+DisplayConfigTargetPreferredMode $targetPreferredModeHeader = New-Object DisplayConfig+DisplayConfigDeviceInfoHeader $targetPreferredModeHeader.id = $this.Id $targetPreferredModeHeader.adapterId = $this.AdapterId $targetPreferredModeHeader.size = [uint32]([System.Runtime.InteropServices.Marshal]::SizeOf($targetPreferredMode)); $targetPreferredModeHeader.type = [DisplayConfig+DisplayConfigDeviceInfoType]::DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_PREFERRED_MODE; $targetPreferredMode.header = $targetPreferredModeHeader $targetPreferredModeResult = [DisplayConfig]::DisplayConfigGetDeviceInfo([ref]$targetPreferredMode); if ($targetPreferredModeResult -ne [Win32Error]::ERROR_SUCCESS) { Write-PSFMessage -Level Critical -Message "Failed to get recommended resolution for target $($this.FriendlyName) (error code $([Win32Error]$targetPreferredModeResult))" return $null } return [Resolution]::new($targetPreferredMode.width, $targetPreferredMode.height) } hidden [bool]_SetHdrEnablement($enablement) { $currentHdrInfo = $this._GetHdrInfo() if ($null -eq $currentHdrInfo) { return $false } if (-not $currentHdrInfo.HdrSupported) { Write-PSFMessage -Level Debug -Message "HDR is not supported for target $($this.FriendlyName), unable to change HDR enablement to $enablement" return $true } if ($currentHdrInfo.HdrEnabled -eq $enablement) { Write-PSFMessage -Level Debug -Message "HDR enablement state is already $enablement for target $($this.FriendlyName), nothing to change" return $true } $setAdvancedColorInfo = New-Object DisplayConfig+DisplayConfigSetAdvancedColorInfo $setAdvancedColorInfoHeader = New-Object DisplayConfig+DisplayConfigDeviceInfoHeader $setAdvancedColorInfoHeader.id = $this.Id $setAdvancedColorInfoHeader.adapterId = $this.AdapterId $setAdvancedColorInfoHeader.size = [uint32]([System.Runtime.InteropServices.Marshal]::SizeOf($setAdvancedColorInfo)); $setAdvancedColorInfoHeader.type = [DisplayConfig+DisplayConfigDeviceInfoType]::DISPLAYCONFIG_DEVICE_INFO_SET_ADVANCED_COLOR_STATE; if ($enablement) { $setAdvancedColorInfo.values = $setAdvancedColorInfo.values -bor [DisplayConfig+DisplayConfigSetAdvancedColorInfoValues]::EnableAdvancedColor } else { $setAdvancedColorInfo.values = $setAdvancedColorInfo.values -band (-bnot [DisplayConfig+DisplayConfigSetAdvancedColorInfoValues]::EnableAdvancedColor) } $setAdvancedColorInfo.header = $setAdvancedColorInfoHeader $setAdvancedColorInfoResult = [DisplayConfig]::DisplayConfigSetDeviceInfo([ref]$setAdvancedColorInfo); if ($setAdvancedColorInfoResult -ne [Win32Error]::ERROR_SUCCESS) { Write-PSFMessage -Level Critical -Message "Failed to set HDR enablement target $($this.FriendlyName) (error code $([Win32Error]$setAdvancedColorInfoResult))" return $false } Write-PSFMessage -Level Verbose -Message "HDR enablement for target $($this.FriendlyName) set successfully to $enablement" return $true } hidden [DisplayConfig+DisplayConfigDeviceInfoHeader]_GetBaseDisplayConfigHeader() { $displayConfigHeader = New-Object DisplayConfig+DisplayConfigDeviceInfoHeader $displayConfigHeader.id = $this.Id $displayConfigHeader.adapterId = $this.AdapterId return $displayConfigHeader } } class Resolution { [uint16]$Width # in pixels [uint16]$Height # in pixels [uint16]$RefreshRate # in fps Resolution() {} Resolution($width, $height) { $this.Width = $width $this.Height = $height } Resolution($width, $height, $refreshRate) { $this.Width = $width $this.Height = $height $this.RefreshRate = $refreshRate } } class HdrInfo { # Whether the display supports HDR [bool]$HdrSupported # Whether HDR is enabled per settings for the display [bool]$HdrEnabled # Bits per color channel aka bit depth [int]$BitDepth HdrInfo() {} HdrInfo($hdrSupported, $hdrEnabled, $bitDepth) { $this.HdrSupported = $hdrSupported $this.HdrEnabled = $hdrEnabled $this.BitDepth = $bitDepth } } # Simple coordinate holder for eg multi monitor settings class Position { [int16]$X [int16]$Y Position() {} Position($xCoordinate, $yCoordinate) { $this.X = $xCoordinate $this.Y = $yCoordinate } } |