using System.Text.Json;
namespace XFESystem.Desktop;
public sealed partial class DesktopSession
{
public const string DisplaySettingsPath = DesktopTheme.SettingsDirectory + "\\Displays.json";
private readonly IDesktopDisplayManager? _displays;
private DesktopDisplayConfiguration? _displayDraft, _displayPrevious;
private string? _displayDrag;
private int _displayDragX, _displayDragY, _displayDragPositionX, _displayDragPositionY;
private double _displayDragScale;
private long _displayRevertAt, _displayIdentifyUntil;
private int _displayCountdown;
public bool IsDisplayChangePending => _displayPrevious is not null;
public string DisplayStatus { get; private set; } = "Select a display to change its settings";
private DesktopBounds TaskbarBounds => new(WorkArea.X, WorkArea.Y + WorkArea.Height, WorkArea.Width, TaskbarHeight);
private DesktopBounds VirtualWindowArea => _displays?.Current.Mode == DesktopDisplayMode.Extend && _displays.Layout.Screens.Count > 1
? new(0, 0, _canvas.Width, _canvas.Height) : WorkArea;
private static DesktopBounds DisplayWorkArea(DesktopBounds screen, bool primary) => screen with { Height = Math.Max(1, screen.Height - (primary ? TaskbarHeight : 0)) };
private DesktopBounds MonitorWorkArea(DesktopBounds window)
{
if (_displays is null) return WorkArea;
var selected = _displays.Layout.Screens.MaxBy(s =>
{
var b = s.Bounds;
return (long)Math.Max(0, Math.Min(window.X + window.Width, b.X + b.Width) - Math.Max(window.X, b.X)) *
Math.Max(0, Math.Min(window.Y + window.Height, b.Y + b.Height) - Math.Max(window.Y, b.Y));
})!;
return DisplayWorkArea(selected.Bounds, selected.IsPrimary);
}
private void LoadDisplaySettings()
{
try
{
if (!_storage.Exists(DisplaySettingsPath)) return;
var saved = DesktopDisplayConfiguration.Parse(_storage.ReadText(DisplaySettingsPath));
if (!_displays!.TryApply(saved, out string? error)) DisplayStatus = "Saved layout no longer matches the displays: " + error;
else DisplayStatus = "Saved display layout restored";
}
catch (Exception error) when (error is IOException or UnauthorizedAccessException or FormatException or JsonException or InvalidOperationException or OverflowException)
{ DisplayStatus = "Display settings could not be restored; using the startup display: " + error.Message; }
}
private void DisplayLayoutChanged()
{
CancelPointerCapture(); IsStartMenuOpen = false; _switchOrder = null;
foreach (var window in _windows)
{
var area = MonitorWorkArea(window.Bounds);
if (window.IsMaximized) window.Bounds = area;
else
{
window.Bounds = Constrain(window.Bounds with { Width = Math.Min(window.Bounds.Width, area.Width), Height = Math.Min(window.Bounds.Height, area.Height) });
window.RestoreBounds = Constrain(window.RestoreBounds);
window.IsSnapped = false;
}
foreach (var page in new[] { window.BrowserSession, window.PreviewSession }) page?.SetCanvas(_canvas);
if (window.BrowserSession is { } browser)
{ var viewport = BrowserViewport(window); browser.ResizeViewport(viewport.Width, viewport.Height); }
if (window.PreviewSession is { } preview)
{ var viewport = PreviewViewport(window); preview.ResizeViewport(viewport.Width, viewport.Height); }
}
if (_displays is not null) (_pointerX, _pointerY) = _displays.Layout.ConstrainPointer(_pointerX, _pointerY);
_hovered = _pressed = null;
NeedsRender = true;
}
public bool ApplyDisplayConfiguration(DesktopDisplayConfiguration configuration)
{
if (_displays is null) { SetDisplayStatus("This runtime has no display management adapter"); return false; }
if (IsDisplayChangePending) { SetDisplayStatus("Keep or revert the current change first"); return false; }
var previous = _displays.Current;
if (!_displays.TryApply(configuration, out string? error)) { SetDisplayStatus(error ?? "Display change failed"); return false; }
_displayPrevious = previous; _displayDraft = configuration;
_displayRevertAt = (_monotonicMilliseconds?.Invoke() ?? _feedbackNow) + 15000;
_displayCountdown = 15;
SetDisplayStatus("Keep these display settings? Reverting in 15 seconds");
return true;
}
public void KeepDisplayConfiguration()
{
if (!IsDisplayChangePending || _displays is null) return;
// Write only confirmed settings; a crash during the preview restores the last good layout.
try
{
_storage.CreateDirectory(DesktopTheme.SettingsDirectory);
_storage.WriteText(DisplaySettingsPath, _displays.Current.ToJson());
SetDisplayStatus(_storage.IsPersistent ? "Display settings saved to disk" : "Display settings saved for this RAM session");
}
catch (Exception error) when (error is IOException or UnauthorizedAccessException or InvalidOperationException)
{ SetDisplayStatus("Display settings kept for this session; saving failed: " + error.Message); }
_displayPrevious = null; _displayRevertAt = 0;
NeedsRender = true;
}
public void RevertDisplayConfiguration()
{
if (_displayPrevious is not { } previous || _displays is null) return;
if (!_displays.TryApply(previous, out string? error)) { SetDisplayStatus("Could not restore the previous layout: " + error); return; }
_displayPrevious = null; _displayRevertAt = 0; _displayDraft = previous;
SetDisplayStatus("Previous display settings restored");
}
private void SetDisplayStatus(string text)
{
DisplayStatus = text; Notification = text;
foreach (var window in _windows.Where(w => w.IsDisplaySettings)) window.Status = text;
NeedsRender = true;
}
private void AdvanceDisplaySettings(long now)
{
if (_displayIdentifyUntil != 0 && now >= _displayIdentifyUntil)
{ _displayIdentifyUntil = 0; _displays?.Identify(false); NeedsRender = true; }
if (!IsDisplayChangePending) return;
if (now >= _displayRevertAt) { RevertDisplayConfiguration(); return; }
int seconds = (int)((_displayRevertAt - now + 999) / 1000);
if (seconds != _displayCountdown)
{ _displayCountdown = seconds; SetDisplayStatus("Keep these display settings? Reverting in " + seconds + " seconds"); }
}
public void ShowDisplaySettings()
{
OpenSettings();
if (ActiveWindow is not { Application: DesktopApplication.Settings } window) return;
window.IsDisplaySettings = true; window.DisplayModeMenu = false; window.Title = "Settings - System > Display";
window.Bounds = Constrain(window.Bounds with { Width = Math.Max(window.Bounds.Width, Math.Min(800, WorkArea.Width)), Height = Math.Max(window.Bounds.Height, Math.Min(600, WorkArea.Height)) });
window.RestoreBounds = window.Bounds;
_displayDraft ??= _displays?.Current;
window.Status = DisplayStatus;
NeedsRender = true;
}
private bool SettingsPageClick(DesktopWindow window, int x, int y)
{
if (y is < 40 or >= 70) return false;
if (!window.IsDisplaySettings && x >= window.Bounds.Width - 152 && x < window.Bounds.Width - 24)
{ ShowDisplaySettings(); return true; }
if (window.IsDisplaySettings && x is >= 24 and < 152)
{ window.IsDisplaySettings = false; window.DisplayModeMenu = false; window.Title = "Settings - Appearance"; window.Status = AppearanceStatus; NeedsRender = true; return true; }
return false;
}
private HitTarget? ResolveSettingsPageTarget(DesktopWindow window, int x, int y)
{
var b = window.Bounds;
var bounds = new DesktopBounds(b.X + (window.IsDisplaySettings ? 24 : b.Width - 152), b.Y + 40, 128, 30);
return bounds.Contains(x, y) ? new(window.IsDisplaySettings ? HitKind.SettingsAppearance : HitKind.SettingsDisplay, window.Id, 0, bounds) : null;
}
private DesktopBounds DisplayDiagram(DesktopWindow window) => new(window.Bounds.X + 24, window.Bounds.Y + 100,
Math.Max(100, window.Bounds.Width - 48), Math.Clamp(window.Bounds.Height - 385, 110, 220));
private int DisplayControlsY(DesktopWindow window) => DisplayDiagram(window).Y + DisplayDiagram(window).Height + 12;
private IEnumerable<HitTarget> DisplayControls(DesktopWindow window)
{
var b = window.Bounds; int y = DisplayControlsY(window);
yield return new(HitKind.DisplayIdentify, window.Id, 0, new(b.X + 24, y, 100, 32));
yield return new(HitKind.DisplayDetect, window.Id, 0, new(b.X + 136, y, 100, 32));
yield return new(HitKind.DisplayMode, window.Id, 0, new(b.X + 248, y, Math.Max(112, b.Width - 272), 32));
yield return new(HitKind.DisplayPrimary, window.Id, 0, new(b.X + 24, y + 76, Math.Min(300, b.Width - 48), 32));
if (IsDisplayChangePending)
{
yield return new(HitKind.DisplayKeep, window.Id, 0, new(b.X + 24, b.Y + b.Height - 74, 148, 34));
yield return new(HitKind.DisplayRevert, window.Id, 0, new(b.X + 184, b.Y + b.Height - 74, 112, 34));
}
else yield return new(HitKind.DisplayApply, window.Id, 0, new(b.X + 24, b.Y + b.Height - 74, 148, 34));
}
private IEnumerable<HitTarget> DisplayModeOptions(DesktopWindow window)
{
var mode = DisplayControls(window).First(t => t.Kind == HitKind.DisplayMode);
for (int i = 0; i < 3; i++) yield return new(HitKind.DisplayModeOption, window.Id, i, mode.Bounds with { Y = mode.Bounds.Y + 34 + i * 34, Height = 34 });
}
private (DesktopDisplay Display, DesktopBounds Bounds, double Scale)[] DisplayTiles(DesktopWindow window)
{
if (_displays is null || _displayDraft is null) return [];
// The arrangement remains visible in Duplicate mode, ready for switching to Extend.
var positions = _displayDraft.Positions;
int left = positions.Min(p => p.X), top = positions.Min(p => p.Y);
int right = positions.Max(p => p.X + _displays.Displays.Single(d => d.Id == p.Id).NativeWidth * 100 / _displays.ScalePercent);
int bottom = positions.Max(p => p.Y + _displays.Displays.Single(d => d.Id == p.Id).NativeHeight * 100 / _displays.ScalePercent);
var area = DisplayDiagram(window);
double scale = Math.Min((area.Width - 36d) / Math.Max(1, right - left), (area.Height - 30d) / Math.Max(1, bottom - top));
double ox = area.X + (area.Width - (right - left) * scale) / 2, oy = area.Y + (area.Height - (bottom - top) * scale) / 2;
return _displays.Displays.Select(d =>
{
var p = positions.Single(p => p.Id == d.Id);
return (d, new DesktopBounds((int)(ox + (p.X - left) * scale), (int)(oy + (p.Y - top) * scale),
Math.Max(10, (int)(d.NativeWidth * 100d / _displays.ScalePercent * scale) - 3), Math.Max(10, (int)(d.NativeHeight * 100d / _displays.ScalePercent * scale) - 3)), scale);
}).ToArray();
}
private HitTarget? ResolveDisplayTarget(DesktopWindow window, int x, int y)
{
if (_displays is null) return null;
if (window.DisplayModeMenu)
foreach (var option in DisplayModeOptions(window)) if (option.Bounds.Contains(x, y)) return option;
foreach (var control in DisplayControls(window)) if (control.Bounds.Contains(x, y)) return control;
var tiles = DisplayTiles(window);
for (int i = 0; i < tiles.Length; i++) if (tiles[i].Bounds.Contains(x, y)) return new(HitKind.DisplayMonitor, window.Id, i, tiles[i].Bounds);
return null;
}
private void DisplaySettingsClick(DesktopWindow window, int x, int y)
{
if (_displays is null || _displayDraft is null) return;
var target = ResolveDisplayTarget(window, x + window.Bounds.X, y + window.Bounds.Y);
if (target is not { } hit) { window.DisplayModeMenu = false; NeedsRender = true; return; }
if (hit.Kind == HitKind.DisplayMonitor)
{
window.SelectedDisplay = hit.Index;
if (!IsDisplayChangePending && _displayDraft.Mode == DesktopDisplayMode.Extend)
{
_displayDrag = _displays.Displays[hit.Index].Id;
var p = _displayDraft.Positions.Single(p => p.Id == _displayDrag);
_displayDragX = x + window.Bounds.X; _displayDragY = y + window.Bounds.Y;
_displayDragPositionX = p.X; _displayDragPositionY = p.Y; _displayDragScale = DisplayTiles(window)[hit.Index].Scale;
}
}
else if (hit.Kind == HitKind.DisplayIdentify)
{ _displays.Identify(true); _displayIdentifyUntil = _feedbackNow + 4000; SetDisplayStatus("Display numbers are shown for four seconds"); }
else if (hit.Kind == HitKind.DisplayDetect) { _displays.Detect(); SetDisplayStatus(_displays.Displays.Count + " display output(s). " + _displays.DetectionMessage); }
else if (hit.Kind == HitKind.DisplayKeep) KeepDisplayConfiguration();
else if (hit.Kind == HitKind.DisplayRevert) RevertDisplayConfiguration();
else if (!IsDisplayChangePending)
{
if (hit.Kind == HitKind.DisplayMode) window.DisplayModeMenu = !window.DisplayModeMenu;
if (hit.Kind == HitKind.DisplayModeOption) { _displayDraft = _displayDraft.WithMode((DesktopDisplayMode)hit.Index); window.DisplayModeMenu = false; }
if (hit.Kind == HitKind.DisplayPrimary) _displayDraft = _displayDraft.WithPrimary(_displays.Displays[window.SelectedDisplay].Id);
if (hit.Kind == HitKind.DisplayApply) ApplyDisplayConfiguration(_displayDraft);
}
NeedsRender = true;
}
private void MoveDisplayDiagram(int x, int y)
{
if (_displayDrag is null || _displays is null || _displayDraft is null) return;
int requestedX = Math.Clamp(_displayDragPositionX + (int)((x - _displayDragX) / _displayDragScale), -16384, 16384);
int requestedY = Math.Clamp(_displayDragPositionY + (int)((y - _displayDragY) / _displayDragScale), -16384, 16384);
var display = _displays.Displays.Single(d => d.Id == _displayDrag);
int width = display.NativeWidth * 100 / _displays.ScalePercent, height = display.NativeHeight * 100 / _displays.ScalePercent;
var candidates = new List<(int X, int Y)>();
foreach (var p in _displayDraft.Positions.Where(p => p.Id != _displayDrag))
{
var other = _displays.Displays.Single(d => d.Id == p.Id);
int w = other.NativeWidth * 100 / _displays.ScalePercent, h = other.NativeHeight * 100 / _displays.ScalePercent;
int alignedX = Math.Abs(requestedX - p.X) < 120 ? p.X : Math.Clamp(requestedX, p.X - width + 1, p.X + w - 1);
int alignedY = Math.Abs(requestedY - p.Y) < 120 ? p.Y : Math.Clamp(requestedY, p.Y - height + 1, p.Y + h - 1);
candidates.Add((p.X - width, alignedY)); candidates.Add((p.X + w, alignedY));
candidates.Add((alignedX, p.Y - height)); candidates.Add((alignedX, p.Y + h));
}
foreach (var candidate in candidates.OrderBy(p => Math.Pow(p.X - requestedX, 2) + Math.Pow(p.Y - requestedY, 2)))
{
var next = _displayDraft.Move(_displayDrag, candidate.X, candidate.Y);
try { DesktopDisplayLayout.Create(_displays.Displays, next, _displays.ScalePercent); }
catch (ArgumentException) { continue; }
_displayDraft = next; NeedsRender = true; break;
}
}
private static string DisplayModeLabel(DesktopDisplayMode mode) => mode switch
{ DesktopDisplayMode.Duplicate => "Duplicate these displays", DesktopDisplayMode.Extend => "Extend these displays", _ => "Show only the main display" };
private void DrawDisplaySettings(DesktopWindow window)
{
var b = window.Bounds;
Button(HitKind.SettingsAppearance, window.Id, 0, new(b.X + 24, b.Y + 40, 128, 30), _theme.SoftAccent);
_canvas.Text("< Appearance", White, b.X + 36, b.Y + 49);
_canvas.Text("System > Display", TextColor, b.X + 174, b.Y + 49);
_canvas.Text("Select a display. Drag its tile to arrange in Extend mode.", Muted, b.X + 24, b.Y + 80);
if (_displays is null || _displayDraft is null) { _canvas.Text("Display management is unavailable in this runtime.", Muted, b.X + 24, b.Y + 118); return; }
var diagram = DisplayDiagram(window);
RoundedFill(_theme.Surface, diagram.X, diagram.Y, diagram.Width, diagram.Height, 10);
var tiles = DisplayTiles(window);
window.SelectedDisplay = Math.Clamp(window.SelectedDisplay, 0, tiles.Length - 1);
for (int i = 0; i < tiles.Length; i++)
{
var tile = tiles[i];
Button(HitKind.DisplayMonitor, window.Id, i, tile.Bounds, i == window.SelectedDisplay ? _theme.AccentStroke : _theme.Border, 5);
uint ink = i == window.SelectedDisplay ? (_theme.IsDark ? 0x08131bu : 0xffffffu) : White;
if (_canvas is IDesktopWebCanvas web)
web.WebText((i + 1).ToString(), ink, tile.Bounds.X + tile.Bounds.Width / 2d - 10, tile.Bounds.Y + tile.Bounds.Height / 2d - 22, 36, "Lato, sans-serif", 700);
else _canvas.Text((i + 1).ToString(), ink, tile.Bounds.X + tile.Bounds.Width / 2 - 4, tile.Bounds.Y + tile.Bounds.Height / 2 - 6);
}
foreach (var control in DisplayControls(window))
{
if (control.Bounds.Y + control.Bounds.Height > b.Y + b.Height - 28) continue;
bool selected = control.Kind == HitKind.DisplayPrimary && _displayDraft.PrimaryId == tiles[window.SelectedDisplay].Display.Id;
Button(control.Kind, window.Id, control.Index, control.Bounds, selected ? _theme.Selection : _theme.SoftAccent);
string label = control.Kind switch
{
HitKind.DisplayIdentify => "Identify", HitKind.DisplayDetect => "Detect",
HitKind.DisplayMode => DisplayModeLabel(_displayDraft.Mode) + " v",
HitKind.DisplayPrimary => selected ? "This is the main display" : "Make this my main display",
HitKind.DisplayKeep => "Keep changes", HitKind.DisplayRevert => "Revert", _ => "Apply changes"
};
_canvas.Text(Clip(label, (control.Bounds.Width - 20) / 8), White, control.Bounds.X + 10, control.Bounds.Y + 10);
}
int y = DisplayControlsY(window); var display = tiles[window.SelectedDisplay].Display;
_canvas.Text(display.Name + " | " + display.NativeWidth + " x " + display.NativeHeight + " | Scale " + _displays.ScalePercent + "%", TextColor, b.X + 24, y + 51);
void Label(string text, int offset)
{ if (y + offset + 20 < b.Y + b.Height - 86) _canvas.Text(Clip(text, (b.Width - 48) / 8), Muted, b.X + 24, y + offset); }
Label(_displayDraft.Mode switch { DesktopDisplayMode.Duplicate => "The same desktop appears on every display; aspect ratio is preserved.",
DesktopDisplayMode.Extend => "Move windows and the pointer across adjoining display edges.", _ => "Only the main display shows the desktop. Other outputs are black." }, 122);
Label("Resolution and scale follow the startup framebuffer for this driver.", 146);
Label("Connect displays before startup. Extra GPU ports need a display driver.", 170);
if (IsDisplayChangePending) _canvas.Text("Reverting in " + _displayCountdown + "s", TextColor, b.X + 316, b.Y + b.Height - 63);
if (window.DisplayModeMenu)
foreach (var option in DisplayModeOptions(window))
{
Button(option.Kind, window.Id, option.Index, option.Bounds, _displayDraft.Mode == (DesktopDisplayMode)option.Index ? _theme.Selection : Panel);
_canvas.Text(Clip(DisplayModeLabel((DesktopDisplayMode)option.Index), (option.Bounds.Width - 20) / 8), White, option.Bounds.X + 10, option.Bounds.Y + 10);
}
}
private void DisplaySettingsKey(DesktopWindow window, DesktopKey key, char character)
{
if (_displays is null || _displayDraft is null) return;
char action = char.ToLowerInvariant(character);
if (key == DesktopKey.Tab) { window.SelectedDisplay = (window.SelectedDisplay + 1) % _displays.Displays.Count; NeedsRender = true; return; }
if (key == DesktopKey.Character && action is >= '1' and <= '8') window.SelectedDisplay = Math.Min(action - '1', _displays.Displays.Count - 1);
else if (key is DesktopKey.Left or DesktopKey.Right) window.SelectedDisplay = (window.SelectedDisplay + (key == DesktopKey.Right ? 1 : -1) + _displays.Displays.Count) % _displays.Displays.Count;
else if (key == DesktopKey.Enter || key == DesktopKey.Save) { if (IsDisplayChangePending) KeepDisplayConfiguration(); else ApplyDisplayConfiguration(_displayDraft); }
else if (key == DesktopKey.Character && action == 'k') KeepDisplayConfiguration();
else if (key == DesktopKey.Character && action == 'r') RevertDisplayConfiguration();
else if (key == DesktopKey.Character && action == 'm' && !IsDisplayChangePending) _displayDraft = _displayDraft.WithMode((DesktopDisplayMode)(((int)_displayDraft.Mode + 1) % 3));
else if (key == DesktopKey.Character && action == 'p' && !IsDisplayChangePending) _displayDraft = _displayDraft.WithPrimary(_displays.Displays[window.SelectedDisplay].Id);
else if (key == DesktopKey.Character && action == 'i')
{ _displays.Identify(true); _displayIdentifyUntil = _feedbackNow + 4000; SetDisplayStatus("Display numbers are shown for four seconds"); }
else if (key == DesktopKey.Character && action == 'd') { _displays.Detect(); SetDisplayStatus(_displays.DetectionMessage); }
else if (key == DesktopKey.Character && action == 'a') { window.IsDisplaySettings = false; window.Title = "Settings - Appearance"; }
NeedsRender = true;
}
public string DisplayStateReport => _displays is null ? "" : "XFE_DESKTOP_DISPLAYS|Mode|" + _displays.Current.Mode + "|Outputs|" + _displays.Displays.Count +
"|Width|" + _canvas.Width + "|Height|" + _canvas.Height + "|Primary|" + (_displays.Displays.ToList().FindIndex(d => d.Id == _displays.Current.PrimaryId) + 1) +
"|Pending|" + (IsDisplayChangePending ? 1 : 0) + "|Identify|" + (_displayIdentifyUntil != 0 ? 1 : 0) + "\n";
}
using System.Text.Json;
namespace XFESystem.Desktop;
public sealed partial class DesktopSession
{
public const string DisplaySettingsPath = DesktopTheme.SettingsDirectory + "\\Displays.json";
private readonly IDesktopDisplayManager? _displays;
private DesktopDisplayConfiguration? _displayDraft, _displayPrevious;
private string? _displayDrag;
private int _displayDragX, _displayDragY, _displayDragPositionX, _displayDragPositionY;
private double _displayDragScale;
private long _displayRevertAt, _displayIdentifyUntil;
private int _displayCountdown;
public bool IsDisplayChangePending => _displayPrevious is not null;
public string DisplayStatus { get; private set; } = "Select a display to change its settings";
private DesktopBounds TaskbarBounds => new(WorkArea.X, WorkArea.Y + WorkArea.Height, WorkArea.Width, TaskbarHeight);
private DesktopBounds VirtualWindowArea => _displays?.Current.Mode == DesktopDisplayMode.Extend && _displays.Layout.Screens.Count > 1
? new(0, 0, _canvas.Width, _canvas.Height) : WorkArea;
private static DesktopBounds DisplayWorkArea(DesktopBounds screen, bool primary) => screen with { Height = Math.Max(1, screen.Height - (primary ? TaskbarHeight : 0)) };
private DesktopBounds MonitorWorkArea(DesktopBounds window)
{
if (_displays is null) return WorkArea;
var selected = _displays.Layout.Screens.MaxBy(s =>
{
var b = s.Bounds;
return (long)Math.Max(0, Math.Min(window.X + window.Width, b.X + b.Width) - Math.Max(window.X, b.X)) *
Math.Max(0, Math.Min(window.Y + window.Height, b.Y + b.Height) - Math.Max(window.Y, b.Y));
})!;
return DisplayWorkArea(selected.Bounds, selected.IsPrimary);
}
private void LoadDisplaySettings()
{
try
{
if (!_storage.Exists(DisplaySettingsPath)) return;
var saved = DesktopDisplayConfiguration.Parse(_storage.ReadText(DisplaySettingsPath));
if (!_displays!.TryApply(saved, out string? error)) DisplayStatus = "Saved layout no longer matches the displays: " + error;
else DisplayStatus = "Saved display layout restored";
}
catch (Exception error) when (error is IOException or UnauthorizedAccessException or FormatException or JsonException or InvalidOperationException or OverflowException)
{ DisplayStatus = "Display settings could not be restored; using the startup display: " + error.Message; }
}
private void DisplayLayoutChanged()
{
CancelPointerCapture(); IsStartMenuOpen = false; _switchOrder = null;
foreach (var window in _windows)
{
var area = MonitorWorkArea(window.Bounds);
if (window.IsMaximized) window.Bounds = area;
else
{
window.Bounds = Constrain(window.Bounds with { Width = Math.Min(window.Bounds.Width, area.Width), Height = Math.Min(window.Bounds.Height, area.Height) });
window.RestoreBounds = Constrain(window.RestoreBounds);
window.IsSnapped = false;
}
foreach (var page in new[] { window.BrowserSession, window.PreviewSession }) page?.SetCanvas(_canvas);
if (window.BrowserSession is { } browser)
{ var viewport = BrowserViewport(window); browser.ResizeViewport(viewport.Width, viewport.Height); }
if (window.PreviewSession is { } preview)
{ var viewport = PreviewViewport(window); preview.ResizeViewport(viewport.Width, viewport.Height); }
}
if (_displays is not null) (_pointerX, _pointerY) = _displays.Layout.ConstrainPointer(_pointerX, _pointerY);
_hovered = _pressed = null;
NeedsRender = true;
}
public bool ApplyDisplayConfiguration(DesktopDisplayConfiguration configuration)
{
if (_displays is null) { SetDisplayStatus("This runtime has no display management adapter"); return false; }
if (IsDisplayChangePending) { SetDisplayStatus("Keep or revert the current change first"); return false; }
var previous = _displays.Current;
if (!_displays.TryApply(configuration, out string? error)) { SetDisplayStatus(error ?? "Display change failed"); return false; }
_displayPrevious = previous; _displayDraft = configuration;
_displayRevertAt = (_monotonicMilliseconds?.Invoke() ?? _feedbackNow) + 15000;
_displayCountdown = 15;
SetDisplayStatus("Keep these display settings? Reverting in 15 seconds");
return true;
}
public void KeepDisplayConfiguration()
{
if (!IsDisplayChangePending || _displays is null) return;
// Write only confirmed settings; a crash during the preview restores the last good layout.
try
{
_storage.CreateDirectory(DesktopTheme.SettingsDirectory);
_storage.WriteText(DisplaySettingsPath, _displays.Current.ToJson());
SetDisplayStatus(_storage.IsPersistent ? "Display settings saved to disk" : "Display settings saved for this RAM session");
}
catch (Exception error) when (error is IOException or UnauthorizedAccessException or InvalidOperationException)
{ SetDisplayStatus("Display settings kept for this session; saving failed: " + error.Message); }
_displayPrevious = null; _displayRevertAt = 0;
NeedsRender = true;
}
public void RevertDisplayConfiguration()
{
if (_displayPrevious is not { } previous || _displays is null) return;
if (!_displays.TryApply(previous, out string? error)) { SetDisplayStatus("Could not restore the previous layout: " + error); return; }
_displayPrevious = null; _displayRevertAt = 0; _displayDraft = previous;
SetDisplayStatus("Previous display settings restored");
}
private void SetDisplayStatus(string text)
{
DisplayStatus = text; Notification = text;
foreach (var window in _windows.Where(w => w.IsDisplaySettings)) window.Status = text;
NeedsRender = true;
}
private void AdvanceDisplaySettings(long now)
{
if (_displayIdentifyUntil != 0 && now >= _displayIdentifyUntil)
{ _displayIdentifyUntil = 0; _displays?.Identify(false); NeedsRender = true; }
if (!IsDisplayChangePending) return;
if (now >= _displayRevertAt) { RevertDisplayConfiguration(); return; }
int seconds = (int)((_displayRevertAt - now + 999) / 1000);
if (seconds != _displayCountdown)
{ _displayCountdown = seconds; SetDisplayStatus("Keep these display settings? Reverting in " + seconds + " seconds"); }
}
public void ShowDisplaySettings()
{
OpenSettings();
if (ActiveWindow is not { Application: DesktopApplication.Settings } window) return;
window.IsDisplaySettings = true; window.DisplayModeMenu = false; window.Title = "Settings - System > Display";
window.Bounds = Constrain(window.Bounds with { Width = Math.Max(window.Bounds.Width, Math.Min(800, WorkArea.Width)), Height = Math.Max(window.Bounds.Height, Math.Min(600, WorkArea.Height)) });
window.RestoreBounds = window.Bounds;
_displayDraft ??= _displays?.Current;
window.Status = DisplayStatus;
NeedsRender = true;
}
private bool SettingsPageClick(DesktopWindow window, int x, int y)
{
if (y is < 40 or >= 70) return false;
if (!window.IsDisplaySettings && x >= window.Bounds.Width - 152 && x < window.Bounds.Width - 24)
{ ShowDisplaySettings(); return true; }
if (window.IsDisplaySettings && x is >= 24 and < 152)
{ window.IsDisplaySettings = false; window.DisplayModeMenu = false; window.Title = "Settings - Appearance"; window.Status = AppearanceStatus; NeedsRender = true; return true; }
return false;
}
private HitTarget? ResolveSettingsPageTarget(DesktopWindow window, int x, int y)
{
var b = window.Bounds;
var bounds = new DesktopBounds(b.X + (window.IsDisplaySettings ? 24 : b.Width - 152), b.Y + 40, 128, 30);
return bounds.Contains(x, y) ? new(window.IsDisplaySettings ? HitKind.SettingsAppearance : HitKind.SettingsDisplay, window.Id, 0, bounds) : null;
}
private DesktopBounds DisplayDiagram(DesktopWindow window) => new(window.Bounds.X + 24, window.Bounds.Y + 100,
Math.Max(100, window.Bounds.Width - 48), Math.Clamp(window.Bounds.Height - 385, 110, 220));
private int DisplayControlsY(DesktopWindow window) => DisplayDiagram(window).Y + DisplayDiagram(window).Height + 12;
private IEnumerable<HitTarget> DisplayControls(DesktopWindow window)
{
var b = window.Bounds; int y = DisplayControlsY(window);
yield return new(HitKind.DisplayIdentify, window.Id, 0, new(b.X + 24, y, 100, 32));
yield return new(HitKind.DisplayDetect, window.Id, 0, new(b.X + 136, y, 100, 32));
yield return new(HitKind.DisplayMode, window.Id, 0, new(b.X + 248, y, Math.Max(112, b.Width - 272), 32));
yield return new(HitKind.DisplayPrimary, window.Id, 0, new(b.X + 24, y + 76, Math.Min(300, b.Width - 48), 32));
if (IsDisplayChangePending)
{
yield return new(HitKind.DisplayKeep, window.Id, 0, new(b.X + 24, b.Y + b.Height - 74, 148, 34));
yield return new(HitKind.DisplayRevert, window.Id, 0, new(b.X + 184, b.Y + b.Height - 74, 112, 34));
}
else yield return new(HitKind.DisplayApply, window.Id, 0, new(b.X + 24, b.Y + b.Height - 74, 148, 34));
}
private IEnumerable<HitTarget> DisplayModeOptions(DesktopWindow window)
{
var mode = DisplayControls(window).First(t => t.Kind == HitKind.DisplayMode);
for (int i = 0; i < 3; i++) yield return new(HitKind.DisplayModeOption, window.Id, i, mode.Bounds with { Y = mode.Bounds.Y + 34 + i * 34, Height = 34 });
}
private (DesktopDisplay Display, DesktopBounds Bounds, double Scale)[] DisplayTiles(DesktopWindow window)
{
if (_displays is null || _displayDraft is null) return [];
// The arrangement remains visible in Duplicate mode, ready for switching to Extend.
var positions = _displayDraft.Positions;
int left = positions.Min(p => p.X), top = positions.Min(p => p.Y);
int right = positions.Max(p => p.X + _displays.Displays.Single(d => d.Id == p.Id).NativeWidth * 100 / _displays.ScalePercent);
int bottom = positions.Max(p => p.Y + _displays.Displays.Single(d => d.Id == p.Id).NativeHeight * 100 / _displays.ScalePercent);
var area = DisplayDiagram(window);
double scale = Math.Min((area.Width - 36d) / Math.Max(1, right - left), (area.Height - 30d) / Math.Max(1, bottom - top));
double ox = area.X + (area.Width - (right - left) * scale) / 2, oy = area.Y + (area.Height - (bottom - top) * scale) / 2;
return _displays.Displays.Select(d =>
{
var p = positions.Single(p => p.Id == d.Id);
return (d, new DesktopBounds((int)(ox + (p.X - left) * scale), (int)(oy + (p.Y - top) * scale),
Math.Max(10, (int)(d.NativeWidth * 100d / _displays.ScalePercent * scale) - 3), Math.Max(10, (int)(d.NativeHeight * 100d / _displays.ScalePercent * scale) - 3)), scale);
}).ToArray();
}
private HitTarget? ResolveDisplayTarget(DesktopWindow window, int x, int y)
{
if (_displays is null) return null;
if (window.DisplayModeMenu)
foreach (var option in DisplayModeOptions(window)) if (option.Bounds.Contains(x, y)) return option;
foreach (var control in DisplayControls(window)) if (control.Bounds.Contains(x, y)) return control;
var tiles = DisplayTiles(window);
for (int i = 0; i < tiles.Length; i++) if (tiles[i].Bounds.Contains(x, y)) return new(HitKind.DisplayMonitor, window.Id, i, tiles[i].Bounds);
return null;
}
private void DisplaySettingsClick(DesktopWindow window, int x, int y)
{
if (_displays is null || _displayDraft is null) return;
var target = ResolveDisplayTarget(window, x + window.Bounds.X, y + window.Bounds.Y);
if (target is not { } hit) { window.DisplayModeMenu = false; NeedsRender = true; return; }
if (hit.Kind == HitKind.DisplayMonitor)
{
window.SelectedDisplay = hit.Index;
if (!IsDisplayChangePending && _displayDraft.Mode == DesktopDisplayMode.Extend)
{
_displayDrag = _displays.Displays[hit.Index].Id;
var p = _displayDraft.Positions.Single(p => p.Id == _displayDrag);
_displayDragX = x + window.Bounds.X; _displayDragY = y + window.Bounds.Y;
_displayDragPositionX = p.X; _displayDragPositionY = p.Y; _displayDragScale = DisplayTiles(window)[hit.Index].Scale;
}
}
else if (hit.Kind == HitKind.DisplayIdentify)
{ _displays.Identify(true); _displayIdentifyUntil = _feedbackNow + 4000; SetDisplayStatus("Display numbers are shown for four seconds"); }
else if (hit.Kind == HitKind.DisplayDetect) { _displays.Detect(); SetDisplayStatus(_displays.Displays.Count + " display output(s). " + _displays.DetectionMessage); }
else if (hit.Kind == HitKind.DisplayKeep) KeepDisplayConfiguration();
else if (hit.Kind == HitKind.DisplayRevert) RevertDisplayConfiguration();
else if (!IsDisplayChangePending)
{
if (hit.Kind == HitKind.DisplayMode) window.DisplayModeMenu = !window.DisplayModeMenu;
if (hit.Kind == HitKind.DisplayModeOption) { _displayDraft = _displayDraft.WithMode((DesktopDisplayMode)hit.Index); window.DisplayModeMenu = false; }
if (hit.Kind == HitKind.DisplayPrimary) _displayDraft = _displayDraft.WithPrimary(_displays.Displays[window.SelectedDisplay].Id);
if (hit.Kind == HitKind.DisplayApply) ApplyDisplayConfiguration(_displayDraft);
}
NeedsRender = true;
}
private void MoveDisplayDiagram(int x, int y)
{
if (_displayDrag is null || _displays is null || _displayDraft is null) return;
int requestedX = Math.Clamp(_displayDragPositionX + (int)((x - _displayDragX) / _displayDragScale), -16384, 16384);
int requestedY = Math.Clamp(_displayDragPositionY + (int)((y - _displayDragY) / _displayDragScale), -16384, 16384);
var display = _displays.Displays.Single(d => d.Id == _displayDrag);
int width = display.NativeWidth * 100 / _displays.ScalePercent, height = display.NativeHeight * 100 / _displays.ScalePercent;
var candidates = new List<(int X, int Y)>();
foreach (var p in _displayDraft.Positions.Where(p => p.Id != _displayDrag))
{
var other = _displays.Displays.Single(d => d.Id == p.Id);
int w = other.NativeWidth * 100 / _displays.ScalePercent, h = other.NativeHeight * 100 / _displays.ScalePercent;
int alignedX = Math.Abs(requestedX - p.X) < 120 ? p.X : Math.Clamp(requestedX, p.X - width + 1, p.X + w - 1);
int alignedY = Math.Abs(requestedY - p.Y) < 120 ? p.Y : Math.Clamp(requestedY, p.Y - height + 1, p.Y + h - 1);
candidates.Add((p.X - width, alignedY)); candidates.Add((p.X + w, alignedY));
candidates.Add((alignedX, p.Y - height)); candidates.Add((alignedX, p.Y + h));
}
foreach (var candidate in candidates.OrderBy(p => Math.Pow(p.X - requestedX, 2) + Math.Pow(p.Y - requestedY, 2)))
{
var next = _displayDraft.Move(_displayDrag, candidate.X, candidate.Y);
try { DesktopDisplayLayout.Create(_displays.Displays, next, _displays.ScalePercent); }
catch (ArgumentException) { continue; }
_displayDraft = next; NeedsRender = true; break;
}
}
private static string DisplayModeLabel(DesktopDisplayMode mode) => mode switch
{ DesktopDisplayMode.Duplicate => "Duplicate these displays", DesktopDisplayMode.Extend => "Extend these displays", _ => "Show only the main display" };
private void DrawDisplaySettings(DesktopWindow window)
{
var b = window.Bounds;
Button(HitKind.SettingsAppearance, window.Id, 0, new(b.X + 24, b.Y + 40, 128, 30), _theme.SoftAccent);
_canvas.Text("< Appearance", White, b.X + 36, b.Y + 49);
_canvas.Text("System > Display", TextColor, b.X + 174, b.Y + 49);
_canvas.Text("Select a display. Drag its tile to arrange in Extend mode.", Muted, b.X + 24, b.Y + 80);
if (_displays is null || _displayDraft is null) { _canvas.Text("Display management is unavailable in this runtime.", Muted, b.X + 24, b.Y + 118); return; }
var diagram = DisplayDiagram(window);
RoundedFill(_theme.Surface, diagram.X, diagram.Y, diagram.Width, diagram.Height, 10);
var tiles = DisplayTiles(window);
window.SelectedDisplay = Math.Clamp(window.SelectedDisplay, 0, tiles.Length - 1);
for (int i = 0; i < tiles.Length; i++)
{
var tile = tiles[i];
Button(HitKind.DisplayMonitor, window.Id, i, tile.Bounds, i == window.SelectedDisplay ? _theme.AccentStroke : _theme.Border, 5);
uint ink = i == window.SelectedDisplay ? (_theme.IsDark ? 0x08131bu : 0xffffffu) : White;
if (_canvas is IDesktopWebCanvas web)
web.WebText((i + 1).ToString(), ink, tile.Bounds.X + tile.Bounds.Width / 2d - 10, tile.Bounds.Y + tile.Bounds.Height / 2d - 22, 36, "Lato, sans-serif", 700);
else _canvas.Text((i + 1).ToString(), ink, tile.Bounds.X + tile.Bounds.Width / 2 - 4, tile.Bounds.Y + tile.Bounds.Height / 2 - 6);
}
foreach (var control in DisplayControls(window))
{
if (control.Bounds.Y + control.Bounds.Height > b.Y + b.Height - 28) continue;
bool selected = control.Kind == HitKind.DisplayPrimary && _displayDraft.PrimaryId == tiles[window.SelectedDisplay].Display.Id;
Button(control.Kind, window.Id, control.Index, control.Bounds, selected ? _theme.Selection : _theme.SoftAccent);
string label = control.Kind switch
{
HitKind.DisplayIdentify => "Identify", HitKind.DisplayDetect => "Detect",
HitKind.DisplayMode => DisplayModeLabel(_displayDraft.Mode) + " v",
HitKind.DisplayPrimary => selected ? "This is the main display" : "Make this my main display",
HitKind.DisplayKeep => "Keep changes", HitKind.DisplayRevert => "Revert", _ => "Apply changes"
};
_canvas.Text(Clip(label, (control.Bounds.Width - 20) / 8), White, control.Bounds.X + 10, control.Bounds.Y + 10);
}
int y = DisplayControlsY(window); var display = tiles[window.SelectedDisplay].Display;
_canvas.Text(display.Name + " | " + display.NativeWidth + " x " + display.NativeHeight + " | Scale " + _displays.ScalePercent + "%", TextColor, b.X + 24, y + 51);
void Label(string text, int offset)
{ if (y + offset + 20 < b.Y + b.Height - 86) _canvas.Text(Clip(text, (b.Width - 48) / 8), Muted, b.X + 24, y + offset); }
Label(_displayDraft.Mode switch { DesktopDisplayMode.Duplicate => "The same desktop appears on every display; aspect ratio is preserved.",
DesktopDisplayMode.Extend => "Move windows and the pointer across adjoining display edges.", _ => "Only the main display shows the desktop. Other outputs are black." }, 122);
Label("Resolution and scale follow the startup framebuffer for this driver.", 146);
Label("Connect displays before startup. Extra GPU ports need a display driver.", 170);
if (IsDisplayChangePending) _canvas.Text("Reverting in " + _displayCountdown + "s", TextColor, b.X + 316, b.Y + b.Height - 63);
if (window.DisplayModeMenu)
foreach (var option in DisplayModeOptions(window))
{
Button(option.Kind, window.Id, option.Index, option.Bounds, _displayDraft.Mode == (DesktopDisplayMode)option.Index ? _theme.Selection : Panel);
_canvas.Text(Clip(DisplayModeLabel((DesktopDisplayMode)option.Index), (option.Bounds.Width - 20) / 8), White, option.Bounds.X + 10, option.Bounds.Y + 10);
}
}
private void DisplaySettingsKey(DesktopWindow window, DesktopKey key, char character)
{
if (_displays is null || _displayDraft is null) return;
char action = char.ToLowerInvariant(character);
if (key == DesktopKey.Tab) { window.SelectedDisplay = (window.SelectedDisplay + 1) % _displays.Displays.Count; NeedsRender = true; return; }
if (key == DesktopKey.Character && action is >= '1' and <= '8') window.SelectedDisplay = Math.Min(action - '1', _displays.Displays.Count - 1);
else if (key is DesktopKey.Left or DesktopKey.Right) window.SelectedDisplay = (window.SelectedDisplay + (key == DesktopKey.Right ? 1 : -1) + _displays.Displays.Count) % _displays.Displays.Count;
else if (key == DesktopKey.Enter || key == DesktopKey.Save) { if (IsDisplayChangePending) KeepDisplayConfiguration(); else ApplyDisplayConfiguration(_displayDraft); }
else if (key == DesktopKey.Character && action == 'k') KeepDisplayConfiguration();
else if (key == DesktopKey.Character && action == 'r') RevertDisplayConfiguration();
else if (key == DesktopKey.Character && action == 'm' && !IsDisplayChangePending) _displayDraft = _displayDraft.WithMode((DesktopDisplayMode)(((int)_displayDraft.Mode + 1) % 3));
else if (key == DesktopKey.Character && action == 'p' && !IsDisplayChangePending) _displayDraft = _displayDraft.WithPrimary(_displays.Displays[window.SelectedDisplay].Id);
else if (key == DesktopKey.Character && action == 'i')
{ _displays.Identify(true); _displayIdentifyUntil = _feedbackNow + 4000; SetDisplayStatus("Display numbers are shown for four seconds"); }
else if (key == DesktopKey.Character && action == 'd') { _displays.Detect(); SetDisplayStatus(_displays.DetectionMessage); }
else if (key == DesktopKey.Character && action == 'a') { window.IsDisplaySettings = false; window.Title = "Settings - Appearance"; }
NeedsRender = true;
}
public string DisplayStateReport => _displays is null ? "" : "XFE_DESKTOP_DISPLAYS|Mode|" + _displays.Current.Mode + "|Outputs|" + _displays.Displays.Count +
"|Width|" + _canvas.Width + "|Height|" + _canvas.Height + "|Primary|" + (_displays.Displays.ToList().FindIndex(d => d.Id == _displays.Current.PrimaryId) + 1) +
"|Pending|" + (IsDisplayChangePending ? 1 : 0) + "|Identify|" + (_displayIdentifyUntil != 0 ? 1 : 0) + "\n";
}