diff --git a/.task/checksum/build b/.task/checksum/build new file mode 100644 index 0000000..3a6f5c8 --- /dev/null +++ b/.task/checksum/build @@ -0,0 +1 @@ +59580c486f374847db3eaf5ef6840550 diff --git a/.task/checksum/restore b/.task/checksum/restore new file mode 100644 index 0000000..f3e596e --- /dev/null +++ b/.task/checksum/restore @@ -0,0 +1 @@ +249f80eb30502a59abbbf97324e45dbe diff --git a/DisplayManager.Linux/Program.cs b/DisplayManager.Linux/Program.cs index efaed5a..99c9919 100644 --- a/DisplayManager.Linux/Program.cs +++ b/DisplayManager.Linux/Program.cs @@ -1,6 +1,6 @@ using Gregghz.DisplayManager.Services; using Gregghz.DisplayManager.Services.Implementations; -using Gregghz.DisplayManager.UI.Cli; +using Gregghz.DisplayManager.Cli; using Gtk; namespace Gregghz.DisplayManager; diff --git a/DisplayManager.Mac/Program.cs b/DisplayManager.Mac/Program.cs index 39bf639..2d04726 100644 --- a/DisplayManager.Mac/Program.cs +++ b/DisplayManager.Mac/Program.cs @@ -1,5 +1,5 @@ using Gregghz.DisplayManager.Services.Implementations; -using Gregghz.DisplayManager.UI.Cli; +using Gregghz.DisplayManager.Cli; namespace Gregghz.DisplayManager; diff --git a/DisplayManager.Windows/App.xaml b/DisplayManager.Windows/App.xaml deleted file mode 100644 index bdd66f8..0000000 --- a/DisplayManager.Windows/App.xaml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/DisplayManager.Windows/App.xaml.cs b/DisplayManager.Windows/App.xaml.cs deleted file mode 100644 index 822c96a..0000000 --- a/DisplayManager.Windows/App.xaml.cs +++ /dev/null @@ -1,42 +0,0 @@ -using Gregghz.DisplayManager.Services.Implementations; -using Gregghz.DisplayManager.UI.Cli; -using Gregghz.DisplayManager.Windows.Services.Implementations; -using Gregghz.DisplayManager.Windows.Views; -using Microsoft.UI.Xaml; - -namespace Gregghz.DisplayManager.Windows; - -public partial class App -{ - public App() - { - InitializeComponent(); - } - - - protected override void OnLaunched(LaunchActivatedEventArgs args) - { - var appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); - var path = Path.Combine(appDataPath, "DisplayManager"); - Directory.CreateDirectory(path); - - // @TODO: DI - var layoutService = new FileSystemLayoutService(path); - var displayService = new WindowsDisplayService(); - - var cliArgs = Environment.GetCommandLineArgs(); - - if (cliArgs.Length == 0 || cliArgs[0].EndsWith(".dll")) - { - var mainWindow = new MainWindow(displayService, layoutService); - mainWindow.Activate(); - } - else - { - // cli stuff - var console = new ConsoleMain(displayService, layoutService); - console.Run(cliArgs); - // Current.Shutdown(); - } - } -} \ No newline at end of file diff --git a/DisplayManager.Windows/DisplayManager.Windows.csproj b/DisplayManager.Windows/DisplayManager.Windows.csproj index cccd98f..85e1365 100644 --- a/DisplayManager.Windows/DisplayManager.Windows.csproj +++ b/DisplayManager.Windows/DisplayManager.Windows.csproj @@ -1,34 +1,22 @@ - WinExe + Exe net8.0-windows10.0.19041.0 10.0.19041.0 10.0.17763 Gregghz.DisplayManager.Windows - app.manifest x86;x64 win-x86;win-x64 win-$(Platform).pubxml - true - true - None - false - false enable enable - - - - - - diff --git a/DisplayManager.Windows/Extensions/SettingsExtensions.cs b/DisplayManager.Windows/Extensions/SettingsExtensions.cs index 9523703..df4b36d 100644 --- a/DisplayManager.Windows/Extensions/SettingsExtensions.cs +++ b/DisplayManager.Windows/Extensions/SettingsExtensions.cs @@ -5,11 +5,12 @@ namespace Gregghz.DisplayManager.Windows.Extensions; public static class SettingsExtensions { - public static Settings FromDevMode(string deviceId, in DEVMODE mode) + public static Settings FromDevMode(string deviceId, bool isConnected, in DEVMODE mode) { return new Settings( deviceId, mode.dmPositionX == 0 && mode.dmPositionY == 0, + isConnected, new Position(mode.dmPositionX, mode.dmPositionY), new Resolution(mode.dmPelsWidth, mode.dmPelsHeight), (Orientation)mode.dmDisplayOrientation, @@ -19,6 +20,12 @@ public static Settings FromDevMode(string deviceId, in DEVMODE mode) public static DEVMODE UpdateDevMode(this Settings settings, ref DEVMODE mode) { + mode.dmFields |= Constants.DM_POSITION | + Constants.DM_PELSWIDTH | + Constants.DM_PELSHEIGHT | + Constants.DM_DISPLAYORIENTATION | + Constants.DM_DISPLAYFREQUENCY; + if (settings.IsPrimary) { mode.dmPositionX = 0; diff --git a/DisplayManager.Windows/Native/Constants.cs b/DisplayManager.Windows/Native/Constants.cs index 7dbf28d..4e5813e 100644 --- a/DisplayManager.Windows/Native/Constants.cs +++ b/DisplayManager.Windows/Native/Constants.cs @@ -3,19 +3,23 @@ namespace Gregghz.DisplayManager.Windows.Native; public static class Constants { public const int ENUM_CURRENT_SETTINGS = -1; - public const uint EDD_GET_DEVICE_INTERFACE_NAME = 0x00000001; + public const int ENUM_REGISTRY_SETTINGS = -2; + + public const int DISPLAY_DEVICE_ATTACHED_TO_DESKTOP = 0x00000001; + public const int DISPLAY_DEVICE_PRIMARY_DEVICE = 0x00000004; + + public const int DM_POSITION = 0x00000020; + public const int DM_DISPLAYORIENTATION = 0x00000080; + public const int DM_PELSWIDTH = 0x00080000; + public const int DM_PELSHEIGHT = 0x00100000; + public const int DM_DISPLAYFREQUENCY = 0x00400000; + + public const uint SDC_APPLY = 0x00000080; + public const uint SDC_TOPOLOGY_EXTEND = 0x00000004; public const int CDS_NONE = 0x00; public const int CDS_UPDATEREGISTRY = 0x01; - public const int CDS_TEST = 0x02; - public const int CDS_FULLSCREEN = 0x04; - public const int CDS_GLOBAL = 0x08; public const int CDS_SET_PRIMARY = 0x10; - public const int CDS_VIDEOPARAMETERS = 0x20; - public const int CDS_ENABLE_UNSAFE_MODES = 0x100; - public const int CDS_DISABLE_UNSAFE_MODES = 0x200; - public const int CDS_RESET = 0x40000000; - public const int CDS_RESET_EX = 0x20000000; public const int CDS_NORESET = 0x10000000; public const int DISP_CHANGE_SUCCESSFUL = 0; diff --git a/DisplayManager.Windows/Native/DisplayAPI.cs b/DisplayManager.Windows/Native/DisplayAPI.cs new file mode 100644 index 0000000..5f0239d --- /dev/null +++ b/DisplayManager.Windows/Native/DisplayAPI.cs @@ -0,0 +1,33 @@ +using System.Runtime.InteropServices; + +namespace Gregghz.DisplayManager.Windows.Native; + +public static class DisplayApi +{ + [DllImport("user32.dll", CharSet = CharSet.Unicode)] + public static extern bool EnumDisplayDevices( + string lpDevice, + uint iDevNum, + ref DISPLAY_DEVICE lpDisplayDevice, + uint dwFlags); + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public struct DISPLAY_DEVICE + { + public int cb; + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] + public string DeviceName; + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] + public string DeviceString; + + public int StateFlags; + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] + public string DeviceID; + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] + public string DeviceKey; + } +} diff --git a/DisplayManager.Windows/Native/DisplayDevice.cs b/DisplayManager.Windows/Native/DisplayDevice.cs deleted file mode 100644 index 1dd85ec..0000000 --- a/DisplayManager.Windows/Native/DisplayDevice.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Runtime.InteropServices; - -namespace Gregghz.DisplayManager.Windows.Native; - -[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] -public struct DISPLAY_DEVICE -{ - public int cb; - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] - public string DeviceName; - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] - public string DeviceString; - public uint StateFlags; - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] - public string DeviceID; - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] - public string DeviceKey; -} \ No newline at end of file diff --git a/DisplayManager.Windows/Native/User32.cs b/DisplayManager.Windows/Native/User32.cs index 98b2811..d778a6d 100644 --- a/DisplayManager.Windows/Native/User32.cs +++ b/DisplayManager.Windows/Native/User32.cs @@ -25,6 +25,14 @@ public static extern int ChangeDisplaySettingsEx(string? lpszDeviceName, IntPtr public static extern bool EnumDisplaySettings(string deviceName, int modeNum, ref DEVMODE devMode); [DllImport("user32.dll", CharSet = CharSet.Auto)] - public static extern bool EnumDisplayDevices(string lpDevice, uint iDevNum, ref DISPLAY_DEVICE lpDisplayDevice, + public static extern bool EnumDisplayDevices(string? lpDevice, uint iDevNum, ref DisplayApi.DISPLAY_DEVICE lpDisplayDevice, uint dwFlags); + + [DllImport("user32.dll")] + public static extern int SetDisplayConfig( + uint numPathArrayElements, + IntPtr pathArray, + uint numModeInfoArrayElements, + IntPtr modeInfoArray, + uint flags); } \ No newline at end of file diff --git a/DisplayManager.Windows/Program.cs b/DisplayManager.Windows/Program.cs new file mode 100644 index 0000000..a30aba1 --- /dev/null +++ b/DisplayManager.Windows/Program.cs @@ -0,0 +1,15 @@ +using Gregghz.DisplayManager.Services.Implementations; +using Gregghz.DisplayManager.Cli; +using Gregghz.DisplayManager.Windows.Services.Implementations; + +var appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); +var path = Path.Combine(appDataPath, "DisplayManager"); +Directory.CreateDirectory(path); + +var layoutService = new FileSystemLayoutService(path); +var displayService = new WindowsDisplayService(); + +var argsToRun = args.Length == 0 ? ["--help"] : args; + +var console = new ConsoleMain(displayService, layoutService); +console.Run(argsToRun); diff --git a/DisplayManager.Windows/Services/Implementations/WindowsDisplayService.cs b/DisplayManager.Windows/Services/Implementations/WindowsDisplayService.cs index 2507e9b..fa0938b 100644 --- a/DisplayManager.Windows/Services/Implementations/WindowsDisplayService.cs +++ b/DisplayManager.Windows/Services/Implementations/WindowsDisplayService.cs @@ -8,27 +8,39 @@ namespace Gregghz.DisplayManager.Windows.Services.Implementations; public class WindowsDisplayService : IDisplayService { + private const string AnsiCyan = "\u001b[36m"; + private const string AnsiGreen = "\u001b[32m"; + private const string AnsiRed = "\u001b[31m"; + private const string AnsiReset = "\u001b[0m"; + + private static DEVMODE CreateDevMode() + { + return new DEVMODE + { + dmSize = (short)Marshal.SizeOf() + }; + } + public Layout GetDisplayLayout() { List foundSettings = []; var deviceMap = GetDeviceMap(); - bool MonitorEnum(IntPtr hMonitor, IntPtr hdcMonitor, ref RECT lprcMonitor, IntPtr dwData) + foreach (var device in GetDisplayDevices()) { - var mi = new MONITORINFOEX(); - User32.GetMonitorInfo(hMonitor, mi); - var deviceName = new string(mi.szDevice).TrimEnd((char)0); - - var mode = new DEVMODE(); - User32.EnumDisplaySettings(deviceName, Constants.ENUM_CURRENT_SETTINGS, ref mode); + var deviceName = device.DeviceName; + if (!deviceMap.TryGetValue(deviceName, out var id)) continue; - var id = deviceMap[deviceName]; - foundSettings.Add(SettingsExtensions.FromDevMode(id, mode)); + var mode = CreateDevMode(); + var hasCurrentMode = User32.EnumDisplaySettings(deviceName, Constants.ENUM_CURRENT_SETTINGS, ref mode); + var hasRegistryMode = hasCurrentMode || + User32.EnumDisplaySettings(deviceName, Constants.ENUM_REGISTRY_SETTINGS, ref mode); + if (!hasRegistryMode) continue; - return true; // Continue enumeration + var isConnected = (device.StateFlags & Constants.DISPLAY_DEVICE_ATTACHED_TO_DESKTOP) != 0; + foundSettings.Add(SettingsExtensions.FromDevMode(id, isConnected, mode)); } - User32.EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, MonitorEnum, IntPtr.Zero); return new Layout(foundSettings); } @@ -36,23 +48,28 @@ public string GetMonitorInfo() { var monitorInfo = ""; - bool MonitorEnum(IntPtr hMonitor, IntPtr hdcMonitor, ref RECT lprcMonitor, IntPtr dwData) + foreach (var device in GetDisplayDevices()) { - var mi = new MONITORINFOEX(); - User32.GetMonitorInfo(hMonitor, mi); - var deviceName = new string(mi.szDevice).TrimEnd((char)0); - - var mode = new DEVMODE(); - User32.EnumDisplaySettings(deviceName, Constants.ENUM_CURRENT_SETTINGS, ref mode); - - monitorInfo += - $"Monitor: {deviceName} ({hMonitor}) - Bounds: {mi.rcMonitor.left}, {mi.rcMonitor.top}, {mi.rcMonitor.right}, {mi.rcMonitor.bottom}\n"; + var deviceName = device.DeviceName; + + var mode = CreateDevMode(); + var hasCurrentMode = User32.EnumDisplaySettings(deviceName, Constants.ENUM_CURRENT_SETTINGS, ref mode); + var hasRegistryMode = hasCurrentMode || + User32.EnumDisplaySettings(deviceName, Constants.ENUM_REGISTRY_SETTINGS, ref mode); + if (!hasRegistryMode) continue; + + var connectionState = + (device.StateFlags & Constants.DISPLAY_DEVICE_ATTACHED_TO_DESKTOP) != 0 ? "Connected" : "Disconnected"; + var stateLabel = connectionState == "Disconnected" + ? $"{AnsiRed}{connectionState}{AnsiReset}" + : $"{AnsiGreen}{connectionState}{AnsiReset}"; + var isPrimary = (device.StateFlags & Constants.DISPLAY_DEVICE_PRIMARY_DEVICE) != 0; + var primaryLabel = isPrimary ? $"{AnsiCyan}PRIMARY{AnsiReset}" : "SECONDARY"; + + monitorInfo += $"Monitor: {deviceName} - State: {stateLabel} - {primaryLabel}\n"; monitorInfo += $"{mode}\r\n\r\n"; - return true; // Continue enumeration } - User32.EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, MonitorEnum, IntPtr.Zero); - return monitorInfo; } @@ -75,22 +92,24 @@ public async Task ApplyLayout(Layout layout) foreach (var s in settings) await UpdateSettings(s.DeviceId, s); - ApplySettings(); + var applyResult = ApplySettings(); + if (applyResult != Constants.DISP_CHANGE_SUCCESSFUL) + await Console.Error.WriteLineAsync($"Desktop apply returned {GetDisplayChangeName(applyResult)} ({applyResult})."); } - private IEnumerable GetDisplayDevices() + private IEnumerable GetDisplayDevices() { - var d = new DISPLAY_DEVICE(); - d.cb = Marshal.SizeOf(d); + var monitor = new DisplayApi.DISPLAY_DEVICE(); + monitor.cb = Marshal.SizeOf(monitor); - List result = new(); + List result = new(); - for (uint id = 0; User32.EnumDisplayDevices(null, id, ref d, 0); id++) + for (uint id = 0; User32.EnumDisplayDevices(null, id, ref monitor, 0); id++) { - result.Add(d); + result.Add(monitor); - d = new DISPLAY_DEVICE(); - d.cb = Marshal.SizeOf(d); + monitor = new DisplayApi.DISPLAY_DEVICE(); + monitor.cb = Marshal.SizeOf(monitor); } return result; @@ -99,21 +118,98 @@ private IEnumerable GetDisplayDevices() private async Task UpdateSettings(string deviceId, Settings settings) { var deviceMap = GetDeviceMap(); - var deviceName = deviceMap[deviceId]; // @TODO: handle missing ID + if (!deviceMap.TryGetValue(deviceId, out var deviceName)) + { + await Console.Error.WriteLineAsync($"Skipping missing display device '{deviceId}'."); + return Constants.DISP_CHANGE_BADPARAM; + } - var mode = new DEVMODE(); - await Task.Run(() => User32.EnumDisplaySettings(deviceName, Constants.ENUM_CURRENT_SETTINGS, ref mode)); + var isCurrentlyConnected = IsDeviceAttachedToDesktop(deviceName); + var shouldAttemptAttach = !isCurrentlyConnected && settings.IsConnected; + + if (shouldAttemptAttach) + { + var topologyResult = EnsureExtendedTopology(); + if (topologyResult != Constants.DISP_CHANGE_SUCCESSFUL) + { + await Console.Error.WriteLineAsync( + $"Topology extend returned {GetDisplayChangeName(topologyResult)} ({topologyResult}) before attaching '{deviceName}'."); + } + } - settings.UpdateDevMode(ref mode); + var mode = CreateDevMode(); + var hasCurrentMode = await Task.Run(() => User32.EnumDisplaySettings(deviceName, Constants.ENUM_CURRENT_SETTINGS, ref mode)); + if (!hasCurrentMode) + await Task.Run(() => User32.EnumDisplaySettings(deviceName, Constants.ENUM_REGISTRY_SETTINGS, ref mode)); + // If both fail the monitor has no base mode; apply entirely from saved settings. - uint dwFlags = Constants.CDS_UPDATEREGISTRY | Constants.CDS_NORESET; + if (settings.IsConnected) + { + settings.UpdateDevMode(ref mode); + } + else + { + // Disable this output in the loaded layout. + mode.dmFields = Constants.DM_POSITION | + Constants.DM_PELSWIDTH | + Constants.DM_PELSHEIGHT; + mode.dmPositionX = 0; + mode.dmPositionY = 0; + mode.dmPelsWidth = 0; + mode.dmPelsHeight = 0; + } + + // Stage all monitors for one final desktop apply. + uint dwFlags = (uint)(Constants.CDS_UPDATEREGISTRY | Constants.CDS_NORESET); if (settings.IsPrimary) dwFlags |= Constants.CDS_SET_PRIMARY; var result = await Task.Run(() => User32.ChangeDisplaySettingsEx(deviceName, ref mode, IntPtr.Zero, dwFlags, IntPtr.Zero)); + + if (result != Constants.DISP_CHANGE_SUCCESSFUL) + await Console.Error.WriteLineAsync( + $"Apply failed for '{deviceName}' ({deviceId}): {GetDisplayChangeName(result)} ({result})."); + return result; } + private static int EnsureExtendedTopology() + { + return User32.SetDisplayConfig( + 0, + IntPtr.Zero, + 0, + IntPtr.Zero, + Constants.SDC_APPLY | Constants.SDC_TOPOLOGY_EXTEND); + } + + private bool IsDeviceAttachedToDesktop(string deviceName) + { + foreach (var device in GetDisplayDevices()) + { + if (!string.Equals(device.DeviceName, deviceName, StringComparison.OrdinalIgnoreCase)) continue; + return (device.StateFlags & Constants.DISPLAY_DEVICE_ATTACHED_TO_DESKTOP) != 0; + } + + return false; + } + + private static string GetDisplayChangeName(int result) + { + return result switch + { + Constants.DISP_CHANGE_SUCCESSFUL => nameof(Constants.DISP_CHANGE_SUCCESSFUL), + Constants.DISP_CHANGE_RESTART => nameof(Constants.DISP_CHANGE_RESTART), + Constants.DISP_CHANGE_FAILED => nameof(Constants.DISP_CHANGE_FAILED), + Constants.DISP_CHANGE_BADMODE => nameof(Constants.DISP_CHANGE_BADMODE), + Constants.DISP_CHANGE_NOTUPDATED => nameof(Constants.DISP_CHANGE_NOTUPDATED), + Constants.DISP_CHANGE_BADFLAGS => nameof(Constants.DISP_CHANGE_BADFLAGS), + Constants.DISP_CHANGE_BADPARAM => nameof(Constants.DISP_CHANGE_BADPARAM), + Constants.DISP_CHANGE_BADDUALVIEW => nameof(Constants.DISP_CHANGE_BADDUALVIEW), + _ => "UNKNOWN_RESULT" + }; + } + private static int ApplySettings(string? deviceName = null) { return User32.ChangeDisplaySettingsEx( diff --git a/DisplayManager.Windows/ViewModels/ApplyLayoutCommand.cs b/DisplayManager.Windows/ViewModels/ApplyLayoutCommand.cs deleted file mode 100644 index 8732567..0000000 --- a/DisplayManager.Windows/ViewModels/ApplyLayoutCommand.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Windows.Input; -using Gregghz.DisplayManager.Services; - -namespace Gregghz.DisplayManager.Windows.ViewModels; - -public class ApplyLayoutCommand(IDisplayService displayService, ILayoutService layoutService) : ICommand -{ - public bool CanExecute(object? parameter) - { - return true; - } - - public async void Execute(object? parameter) - { - if (parameter is not string layoutName) return; - var layout = await layoutService.GetLayout(layoutName); - if (layout is not null) await displayService.ApplyLayout(layout); - // @TODO: what to do if layout is null? - } - - public event EventHandler? CanExecuteChanged; -} \ No newline at end of file diff --git a/DisplayManager.Windows/ViewModels/MainWindowViewModel.cs b/DisplayManager.Windows/ViewModels/MainWindowViewModel.cs deleted file mode 100644 index 74edab3..0000000 --- a/DisplayManager.Windows/ViewModels/MainWindowViewModel.cs +++ /dev/null @@ -1,122 +0,0 @@ -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.Runtime.CompilerServices; -using System.Windows.Input; -using Gregghz.DisplayManager.Model; -using Gregghz.DisplayManager.Services; - -namespace Gregghz.DisplayManager.Windows.ViewModels; - -public class MainWindowViewModel : INotifyPropertyChanged -{ - private readonly ILayoutService _layoutService; - public readonly ICommand ApplyLayoutCommand; - public readonly OpenSavePopupCommand OpenSavePopupCommand; - public readonly SaveLayoutCommand SaveLayoutCommand; - private bool _applyEnabled; - private string? _selectedLayout; - - public MainWindowViewModel(IDisplayService displayService, ILayoutService layoutService) - { - _layoutService = layoutService; - ApplyLayoutCommand = new ApplyLayoutCommand(displayService, layoutService); - OpenSavePopupCommand = new OpenSavePopupCommand(displayService); - SaveLayoutCommand = new SaveLayoutCommand(displayService, layoutService); - - Layouts.CollectionChanged += (_, args) => - { - var newCount = args.NewItems?.Count ?? 0; - var oldCount = args.OldItems?.Count ?? 0; - if (newCount != oldCount) LayoutCountChanged?.Invoke(this, newCount); - }; - - PopulateLayouts(); - } - - public ObservableCollection Layouts { get; } = new(); - - public bool ApplyEnabled - { - get => _applyEnabled; - set - { - if (_applyEnabled != value) - { - _applyEnabled = value; - OnPropertyChanged(); - } - } - } - - public string? SelectedLayout - { - get => _selectedLayout; - set - { - if (_selectedLayout != value) - { - _selectedLayout = value; - ApplyEnabled = value is not null; - TriggerLayoutSelected(); - - OnPropertyChanged(); - } - } - } - - public event PropertyChangedEventHandler? PropertyChanged; - - private async void PopulateLayouts() - { - var layouts = await _layoutService.GetSavedLayouts(); - foreach (var layout in layouts) Layouts.Add(layout); - } - - private async void TriggerLayoutSelected() - { - if (_selectedLayout is null) return; - var layout = await _layoutService.GetLayout(_selectedLayout); - if (layout is not null) LayoutSelected?.Invoke(this, layout); - } - - public void SubscribeToLayoutCountChanged(Action onChanged) - { - LayoutCountChanged += (_, count) => onChanged(count); - onChanged(Layouts.Count); - } - - public void SubscribeToLayoutSelected(Action onSelected) - { - LayoutSelected += (_, layout) => onSelected(layout); - } - - public void SubscribeToLayoutSaved(Action> onSaved) - { - SaveLayoutCommand.LayoutSaved += async (_, _) => - { - var layouts = await _layoutService.GetSavedLayouts(); - onSaved(layouts); - }; - } - - public event EventHandler? LayoutCountChanged; - public event EventHandler? LayoutSelected; - - public void SubscribeToSaveDialogRequested(EventHandler handler) - { - OpenSavePopupCommand.SubscribeToSaveDialogRequest(handler); - } - - protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) - { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - } - - protected bool SetField(ref T field, T value, [CallerMemberName] string? propertyName = null) - { - if (EqualityComparer.Default.Equals(field, value)) return false; - field = value; - OnPropertyChanged(propertyName); - return true; - } -} \ No newline at end of file diff --git a/DisplayManager.Windows/ViewModels/OpenSavePopupCommand.cs b/DisplayManager.Windows/ViewModels/OpenSavePopupCommand.cs deleted file mode 100644 index 83c396c..0000000 --- a/DisplayManager.Windows/ViewModels/OpenSavePopupCommand.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Windows.Input; -using Gregghz.DisplayManager.Model; -using Gregghz.DisplayManager.Services; - -namespace Gregghz.DisplayManager.Windows.ViewModels; - -public class OpenSavePopupCommand(IDisplayService displayService) : ICommand -{ - public bool CanExecute(object? parameter) - { - return true; - } - - public void Execute(object? parameter) - { - var currentLayout = displayService.GetDisplayLayout(); - SaveDialogRequested?.Invoke(this, currentLayout); - } - - public event EventHandler? CanExecuteChanged; - - public void SubscribeToSaveDialogRequest(EventHandler f) - { - SaveDialogRequested += f; - } - - public event EventHandler? SaveDialogRequested; -} \ No newline at end of file diff --git a/DisplayManager.Windows/ViewModels/SaveDialogViewModel.cs b/DisplayManager.Windows/ViewModels/SaveDialogViewModel.cs deleted file mode 100644 index ffdcce7..0000000 --- a/DisplayManager.Windows/ViewModels/SaveDialogViewModel.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System.ComponentModel; -using System.Runtime.CompilerServices; -using Microsoft.UI.Xaml.Controls; - -namespace Gregghz.DisplayManager.Windows.ViewModels; - -public class SaveDialogViewModel(Canvas canvas) : INotifyPropertyChanged -{ - private string? _layoutName; - - public string? LayoutName - { - get => _layoutName; - set - { - if (_layoutName == value) return; - _layoutName = value; - OnPropertyChanged(); - } - } - - public Canvas Canvas - { - get => canvas; - set - { - canvas = value; - OnPropertyChanged(); - } - } - - public event PropertyChangedEventHandler? PropertyChanged; - - protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) - { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - } - - protected bool SetField(ref T field, T value, [CallerMemberName] string? propertyName = null) - { - if (EqualityComparer.Default.Equals(field, value)) return false; - field = value; - OnPropertyChanged(propertyName); - return true; - } -} \ No newline at end of file diff --git a/DisplayManager.Windows/ViewModels/SaveLayoutCommand.cs b/DisplayManager.Windows/ViewModels/SaveLayoutCommand.cs deleted file mode 100644 index 9db557d..0000000 --- a/DisplayManager.Windows/ViewModels/SaveLayoutCommand.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Windows.Input; -using Gregghz.DisplayManager.Services; - -namespace Gregghz.DisplayManager.Windows.ViewModels; - -public class SaveLayoutCommand(IDisplayService displayService, ILayoutService layoutService) : ICommand -{ - public bool CanExecute(object? parameter) - { - return true; - } - - public async void Execute(object? parameter) - { - if (parameter is not Func getter) return; - - var value = getter(); - var currentLayout = displayService.GetDisplayLayout(); - await layoutService.SaveLayout(value, currentLayout); - LayoutSaved?.Invoke(this, EventArgs.Empty); - } - - public event EventHandler? CanExecuteChanged; - public event EventHandler? LayoutSaved; -} \ No newline at end of file diff --git a/DisplayManager.Windows/Views/Dialogs/SaveDialogContent.xaml b/DisplayManager.Windows/Views/Dialogs/SaveDialogContent.xaml deleted file mode 100644 index 038198b..0000000 --- a/DisplayManager.Windows/Views/Dialogs/SaveDialogContent.xaml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/DisplayManager.Windows/Views/Dialogs/SaveDialogContent.xaml.cs b/DisplayManager.Windows/Views/Dialogs/SaveDialogContent.xaml.cs deleted file mode 100644 index f6c5723..0000000 --- a/DisplayManager.Windows/Views/Dialogs/SaveDialogContent.xaml.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Gregghz.DisplayManager.Windows.ViewModels; - -namespace Gregghz.DisplayManager.Windows.Views.Dialogs; - -public sealed partial class SaveDialogContent -{ - public SaveDialogContent() - { - InitializeComponent(); - ViewModel = new SaveDialogViewModel(PreviewCanvas); - DataContext = ViewModel; - } - - public SaveDialogViewModel ViewModel { get; set; } -} \ No newline at end of file diff --git a/DisplayManager.Windows/Views/MainWindow.xaml b/DisplayManager.Windows/Views/MainWindow.xaml deleted file mode 100644 index 98e01e2..0000000 --- a/DisplayManager.Windows/Views/MainWindow.xaml +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - -