Repository navigation
Expand file tree
/
Copy pathDisplayLayoutPlanner.cs
More file actions
226 lines (200 loc) · 9.25 KB
/
Copy pathDisplayLayoutPlanner.cs
File metadata and controls
226 lines (200 loc) · 9.25 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
namespace WindowsDeviceControl;
/// <summary>
/// Validates layout geometry and plans distinct source routes over supplied CCD arrays.
/// Performs no native calls itself; monitor identity is resolved by the supplied callback.
/// </summary>
internal static class DisplayLayoutPlanner
{
/// <summary>Why this layout cannot describe a desktop, or null when it can.</summary>
/// <param name="layout">The layout to check.</param>
/// <returns>The first rule the layout breaks, or null.</returns>
internal static DisplayLayoutProblem? Describe(DisplayLayout layout)
{
if (layout.Outputs is not { Count: > 0 })
{
return DisplayLayoutProblem.NoDisplays;
}
if (layout.Outputs.Any(output => output.Width < 320 || output.Height < 200
|| output.Width > 32768 || output.Height > 32768))
{
return DisplayLayoutProblem.ResolutionOutOfRange;
}
if (layout.Outputs.Count(output => output.IsPrimary) != 1)
{
return DisplayLayoutProblem.PrimaryNotAtOrigin;
}
for (var index = 0; index < layout.Outputs.Count; index++)
{
for (var other = index + 1; other < layout.Outputs.Count; other++)
{
if (layout.Outputs[index].Target.Matches(layout.Outputs[other].Target))
{
return DisplayLayoutProblem.DuplicateDisplay;
}
if (Overlaps(layout.Outputs[index], layout.Outputs[other]))
{
return DisplayLayoutProblem.Overlap;
}
}
}
if (layout.Outputs.Any(output => output.DpiPercent is { } percent && (percent < 100 || percent > 500)))
{
return DisplayLayoutProblem.ScalingOutOfRange;
}
return Connected(layout) ? null : DisplayLayoutProblem.Detached;
}
private static bool Overlaps(DisplayLayoutOutput first, DisplayLayoutOutput second)
{
return first.X < second.X + second.Width && second.X < first.X + first.Width
&& first.Y < second.Y + second.Height &&
second.Y < first.Y + first.Height;
}
/// <summary>Whether every display touches the arrangement built from the primary outwards.</summary>
private static bool Connected(DisplayLayout layout)
{
List<DisplayLayoutOutput> reached = [layout.Outputs.First(output => output.IsPrimary)];
for (var index = 0; index < reached.Count; index++)
{
var current = reached[index];
foreach (var candidate in layout.Outputs)
{
if (!reached.Contains(candidate) && Touches(candidate, current))
{
reached.Add(candidate);
}
}
}
return reached.Count == layout.Outputs.Count;
}
private static bool Touches(DisplayLayoutOutput first, DisplayLayoutOutput second)
{
return first.X <= second.X + second.Width && second.X <= first.X + first.Width
&& first.Y <= second.Y + second.Height &&
second.Y <= first.Y + first.Height;
}
/// <summary>Builds the paths and modes for one layout from the adapter's own path list.</summary>
/// <param name="paths">Every path the adapter advertises, from a QDC_ALL_PATHS query.</param>
/// <param name="layout">The layout to express.</param>
/// <param name="readTarget">Reads a path's monitor identity.</param>
/// <returns>
/// The configuration to supply to Windows, whose first paths follow the layout's outputs in order;
/// or, with empty arrays, the problem (<see cref="DisplayLayoutProblem.NoDisplayPath" /> or
/// <see cref="DisplayLayoutProblem.NoFreeSource" />) and the display it concerns.
/// </returns>
internal static (DisplayTopology.PathInfo[] Paths, DisplayTopology.ModeInfo[] Modes, DisplayLayoutProblem? Problem,
DisplayTargetIdentity? ProblemTarget) Plan(
DisplayTopology.PathInfo[] paths,
DisplayLayout layout,
Func<DisplayTopology.PathInfo, DisplayTargetIdentity> readTarget)
{
var candidates = paths
.Select(path => (Path: path, Target: Identity(path, readTarget)))
.Where(candidate => candidate.Target is not null)
.ToArray();
List<DisplayTopology.PathInfo> plannedPaths = [];
List<DisplayTopology.ModeInfo> plannedModes = [];
HashSet<DisplayTopology.RouteKey> takenSources = [];
foreach (var output in layout.Outputs)
{
var matching = candidates.Where(candidate => output.Target.Matches(candidate.Target!)).ToArray();
if (matching.Length == 0)
{
return ([], [], DisplayLayoutProblem.NoDisplayPath, output.Target);
}
if (matching.Select(candidate => candidate.Path.TargetRoute).Distinct().Count() > 1)
{
return ([], [], DisplayLayoutProblem.NoDisplayPath, output.Target);
}
// Preserve an active source before considering alternate routes.
var chosen = matching.FirstOrDefault(candidate =>
(candidate.Path.Flags & DisplayTopology.PathActiveFlag) != 0
&& !takenSources.Contains(candidate.Path.SourceRoute));
if (chosen.Target is null)
{
chosen = matching.Where(candidate => !takenSources.Contains(candidate.Path.SourceRoute))
.OrderBy(candidate => candidate.Path.SourceInfo.Id)
.FirstOrDefault();
}
if (chosen.Target is null)
{
return ([], [], DisplayLayoutProblem.NoFreeSource, output.Target);
}
takenSources.Add(chosen.Path.SourceRoute);
// Capture the preserved rotation before changing the path's active flag.
var current = matching.FirstOrDefault(candidate =>
(candidate.Path.Flags & DisplayTopology.PathActiveFlag) != 0);
var rotation = output.Rotation != 0
? output.Rotation
: current.Target is not null && current.Path.TargetInfo.Rotation != 0
? current.Path.TargetInfo.Rotation
: 1;
var path = chosen.Path;
path.Flags |= DisplayTopology.PathActiveFlag;
path.SourceInfo.ModeInfoIdx = (uint)plannedModes.Count;
// The target mode is left to Windows: the requested refresh plus ALLOW_CHANGES is what
// lets the driver pick a signal it can actually produce for this resolution.
path.TargetInfo.ModeInfoIdx = DisplayTopology.InvalidModeIndex;
path.TargetInfo.RefreshRate = new DisplayTopology.Rational
{
Numerator = output.Refresh.Numerator,
Denominator = output.Refresh.Denominator
};
// Inactive CCD paths have unspecified scan ordering. Pair the requested refresh with
// progressive scan instead of replaying that placeholder (Windows rejects it with 87).
path.TargetInfo.ScanLineOrdering = 1;
path.TargetInfo.Rotation = rotation;
plannedPaths.Add(path);
plannedModes.Add(new DisplayTopology.ModeInfo
{
InfoType = DisplayTopology.SourceModeType,
Id = path.SourceInfo.Id,
AdapterId = path.SourceInfo.AdapterId,
Mode = new DisplayTopology.ModeUnion
{
Source = new DisplayTopology.SourceMode
{
Width = (uint)output.Width,
Height = (uint)output.Height,
PixelFormat = DisplayTopology.Pixel32Bpp,
X = output.X,
Y = output.Y
}
}
});
}
// A supplied configuration must explicitly deactivate omitted paths.
foreach (var candidate in candidates)
{
if (plannedPaths.Exists(planned => Same(planned, candidate.Path)))
{
continue;
}
var path = candidate.Path;
path.Flags &= ~DisplayTopology.PathActiveFlag;
path.SourceInfo.ModeInfoIdx = DisplayTopology.InvalidModeIndex;
path.TargetInfo.ModeInfoIdx = DisplayTopology.InvalidModeIndex;
plannedPaths.Add(path);
}
return ([.. plannedPaths], [.. plannedModes], null, null);
}
private static bool Same(DisplayTopology.PathInfo first, DisplayTopology.PathInfo second)
{
return first.TargetRoute == second.TargetRoute && first.SourceRoute == second.SourceRoute;
}
private static DisplayTargetIdentity? Identity(
DisplayTopology.PathInfo path, Func<DisplayTopology.PathInfo, DisplayTargetIdentity> readTarget)
{
try
{
return readTarget(path);
}
catch (Win32Exception)
{
return null;
}
}
}