Repository navigation
Expand file tree
/
Copy pathDisplayMonitorResolver.cs
More file actions
180 lines (153 loc) · 7.48 KB
/
Copy pathDisplayMonitorResolver.cs
File metadata and controls
180 lines (153 loc) · 7.48 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
namespace AmpUp;
internal sealed class DisplayMonitorInfo
{
public int Index { get; init; }
public string DeviceName { get; init; } = "";
public string FriendlyName { get; init; } = "";
public string DevicePath { get; init; } = "";
/// <summary>Every monitor showing this screen (several when mirrored); saved paths match any.</summary>
public IReadOnlyList<string> AllDevicePaths { get; init; } = Array.Empty<string>();
public bool IsPrimary { get; init; }
/// <summary>1, 2, 3… left to right (then top to bottom). Follows where the screens sit, so it
/// survives power cycles; Identify flashes it on each screen.</summary>
public int Number { get; set; }
/// <summary>Work area in physical pixels.</summary>
public System.Drawing.Rectangle WorkingArea { get; init; }
internal System.Drawing.Rectangle Bounds { get; init; }
public string DisplayName => !string.IsNullOrWhiteSpace(FriendlyName) ? FriendlyName : DeviceName;
public string NumberedName => $"{Number} · {DisplayName}";
public string Label => IsPrimary ? $"{NumberedName} (Primary)" : NumberedName;
}
internal static class DisplayMonitorResolver
{
public static List<DisplayMonitorInfo> GetMonitors()
{
var screens = System.Windows.Forms.Screen.AllScreens;
var displayConfig = NativeMethods.GetMonitorDisplayConfigInfo();
var monitors = new List<DisplayMonitorInfo>(screens.Length);
for (int i = 0; i < screens.Length; i++)
{
displayConfig.TryGetValue(screens[i].DeviceName, out var info);
monitors.Add(new DisplayMonitorInfo
{
Index = i,
DeviceName = screens[i].DeviceName,
FriendlyName = info?.FriendlyName ?? "",
DevicePath = info?.DevicePath ?? "",
AllDevicePaths = info?.AllDevicePaths ?? Array.Empty<string>(),
IsPrimary = screens[i].Primary,
WorkingArea = screens[i].WorkingArea,
Bounds = screens[i].Bounds,
});
}
int number = 1;
foreach (var m in monitors.OrderBy(m => m.Bounds.Left).ThenBy(m => m.Bounds.Top))
m.Number = number++;
return monitors;
}
/// <summary>"2 · VS32254K" for a GDI device name (\\.\DISPLAY3), or the plain name if
/// that screen isn't connected.</summary>
public static string NumberedName(IReadOnlyList<DisplayMonitorInfo> monitors, string deviceName, string name)
{
var m = monitors.FirstOrDefault(x => string.Equals(x.DeviceName.TrimEnd('\0'), deviceName.TrimEnd('\0'), StringComparison.OrdinalIgnoreCase));
return m == null ? name : $"{m.Number} · {name}";
}
public static int ResolveOsdMonitorIndex(OsdConfig osd)
{
var monitors = GetMonitors();
if (monitors.Count == 0)
return 0;
var match = FindMonitor(monitors, osd.MonitorDevicePath, osd.MonitorFriendlyName, osd.MonitorDeviceName);
if (match != null)
{
if (string.IsNullOrWhiteSpace(osd.MonitorDevicePath) && MatchedByUniqueName(monitors, match, osd.MonitorFriendlyName))
osd.MonitorDevicePath = match.DevicePath; // older config: pin it to the hardware path
return match.Index;
}
if (osd.MonitorIndex >= 0 && osd.MonitorIndex < monitors.Count)
return osd.MonitorIndex;
return monitors.FirstOrDefault(m => m.IsPrimary)?.Index ?? 0;
}
public static void RememberOsdMonitor(OsdConfig osd, int monitorIndex)
{
var monitors = GetMonitors();
var monitor = monitors.FirstOrDefault(m => m.Index == monitorIndex);
if (monitor == null)
return;
osd.MonitorIndex = monitor.Index;
osd.MonitorDeviceName = monitor.DeviceName;
osd.MonitorFriendlyName = monitor.FriendlyName;
osd.MonitorDevicePath = monitor.DevicePath;
}
public static int ResolveScreenSyncMonitorIndex(ScreenSyncConfig cfg)
{
var monitors = GetMonitors();
if (monitors.Count == 0)
return 0;
var match = FindMonitor(monitors, cfg.MonitorDevicePath, cfg.MonitorFriendlyName, cfg.MonitorDeviceName);
if (match != null)
{
if (string.IsNullOrWhiteSpace(cfg.MonitorDevicePath) && MatchedByUniqueName(monitors, match, cfg.MonitorFriendlyName))
cfg.MonitorDevicePath = match.DevicePath; // older config: pin it to the hardware path
return match.Index;
}
if (cfg.MonitorIndex >= 0 && cfg.MonitorIndex < monitors.Count)
return cfg.MonitorIndex;
return monitors.FirstOrDefault(m => m.IsPrimary)?.Index ?? 0;
}
public static void RememberScreenSyncMonitor(ScreenSyncConfig cfg, int monitorIndex)
{
var monitor = GetMonitors().FirstOrDefault(m => m.Index == monitorIndex);
if (monitor == null)
return;
cfg.MonitorIndex = monitor.Index;
cfg.MonitorDeviceName = monitor.DeviceName;
cfg.MonitorFriendlyName = monitor.FriendlyName;
cfg.MonitorDevicePath = monitor.DevicePath;
}
/// <summary>
/// The hardware path survives power cycles and tells identical monitors apart. A friendly
/// name only counts when one screen has it (two of the same model share it). GDI names
/// (\\.\DISPLAY1) get renumbered when screens power off and on, so they're only trusted
/// for configs saved before the hardware path was recorded (issue #29).
/// </summary>
private static DisplayMonitorInfo? FindMonitor(
List<DisplayMonitorInfo> monitors, string? devicePath, string? friendlyName, string? deviceName)
{
var match = FindByIdentity(monitors, devicePath, m => m.DevicePath);
if (match != null)
return match;
if (!string.IsNullOrWhiteSpace(devicePath))
{
match = monitors.FirstOrDefault(m => m.AllDevicePaths.Contains(devicePath, StringComparer.OrdinalIgnoreCase));
if (match != null)
return match;
}
if (!string.IsNullOrWhiteSpace(friendlyName))
{
var named = monitors.Where(m => string.Equals(m.FriendlyName, friendlyName, StringComparison.OrdinalIgnoreCase)).ToList();
if (named.Count == 1)
return named[0];
}
return string.IsNullOrWhiteSpace(devicePath)
? FindByIdentity(monitors, deviceName, m => m.DeviceName)
: null;
}
/// <summary>True when <paramref name="monitor"/> was found by a saved friendly name no other
/// screen shares, so its hardware path can safely be saved in place of the old identity.</summary>
private static bool MatchedByUniqueName(List<DisplayMonitorInfo> monitors, DisplayMonitorInfo monitor, string? savedFriendlyName) =>
!string.IsNullOrWhiteSpace(monitor.DevicePath)
&& string.Equals(monitor.FriendlyName, savedFriendlyName, StringComparison.OrdinalIgnoreCase)
&& !string.IsNullOrWhiteSpace(monitor.FriendlyName)
&& monitors.Count(m => string.Equals(m.FriendlyName, monitor.FriendlyName, StringComparison.OrdinalIgnoreCase)) == 1;
private static DisplayMonitorInfo? FindByIdentity(
List<DisplayMonitorInfo> monitors,
string? storedValue,
Func<DisplayMonitorInfo, string> selector)
{
if (string.IsNullOrWhiteSpace(storedValue))
return null;
return monitors.FirstOrDefault(m =>
string.Equals(selector(m), storedValue, StringComparison.OrdinalIgnoreCase));
}
}