diff --git a/TheXamlGuy.TaskbarGroup.Core/FileConfigurationExtensions.cs b/TheXamlGuy.TaskbarGroup.Core/FileConfigurationExtensions.cs new file mode 100644 index 0000000..b3e27f2 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/FileConfigurationExtensions.cs @@ -0,0 +1,19 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.FileProviders; + +namespace TheXamlGuy.TaskbarGroup.Core +{ + public static class FileConfigurationExtensions + { + public static IConfigurationBuilder SetBasePath(this IConfigurationBuilder builder, + string basePath, bool createDirectory) + { + if (!Directory.Exists(basePath) && createDirectory) + { + Directory.CreateDirectory(basePath); + } + + return builder.SetFileProvider(new PhysicalFileProvider(basePath)); + } + } +} diff --git a/TheXamlGuy.TaskbarGroup.Core/HostingHostBuilderExtensions.cs b/TheXamlGuy.TaskbarGroup.Core/HostingHostBuilderExtensions.cs new file mode 100644 index 0000000..5dabd7f --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/HostingHostBuilderExtensions.cs @@ -0,0 +1,17 @@ +using Microsoft.Extensions.Hosting; + +namespace TheXamlGuy.TaskbarGroup.Core +{ + public static class HostingHostBuilderExtensions + { + public static IHostBuilder UseContentRoot(this IHostBuilder hostBuilder, string contentRoot, bool createDirectory) + { + if (!Directory.Exists(contentRoot) && createDirectory) + { + Directory.CreateDirectory(contentRoot); + } + + return hostBuilder.UseContentRoot(contentRoot); + } + } +} diff --git a/TheXamlGuy.TaskbarGroup.Core/IServiceCollectionExtensions.cs b/TheXamlGuy.TaskbarGroup.Core/IServiceCollectionExtensions.cs index e8fcaf4..58e029d 100644 --- a/TheXamlGuy.TaskbarGroup.Core/IServiceCollectionExtensions.cs +++ b/TheXamlGuy.TaskbarGroup.Core/IServiceCollectionExtensions.cs @@ -2,6 +2,23 @@ namespace TheXamlGuy.TaskbarGroup.Core { + public static class PointerLocationExtensions + { + public static bool IsWithinBounds(this PointerLocation args, Rect bounds) + { + if (args.X >= bounds.X + && args.X <= bounds.X + bounds.Width + && args.Y >= bounds.Y + && args.Y <= bounds.Y + bounds.Height) + { + return true; + } + else + { + return false; + } + } + } public static class IServiceCollectionExtensions { public static IServiceCollection AddAsyncHandler(this IServiceCollection serviceCollection) @@ -55,11 +72,12 @@ namespace TheXamlGuy.TaskbarGroup.Core .AddSingleton() .AddSingleton() .AddSingleton() - .AddSingleton() + .AddSingleton() .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton(); + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton(); } } } diff --git a/TheXamlGuy.TaskbarGroup.Core/ITaskbar.cs b/TheXamlGuy.TaskbarGroup.Core/ITaskbar.cs index ec095ac..63b83da 100644 --- a/TheXamlGuy.TaskbarGroup.Core/ITaskbar.cs +++ b/TheXamlGuy.TaskbarGroup.Core/ITaskbar.cs @@ -1,8 +1,9 @@ - -namespace TheXamlGuy.TaskbarGroup.Core +namespace TheXamlGuy.TaskbarGroup.Core { - public interface ITaskbar + public interface ITaskbar : IInitializable, IDisposable { TaskbarState GetCurrentState(); + + IntPtr GetHandle(); } } \ No newline at end of file diff --git a/TheXamlGuy.TaskbarGroup.Core/ITaskbarButton.cs b/TheXamlGuy.TaskbarGroup.Core/ITaskbarButton.cs index 2191ccc..1d24308 100644 --- a/TheXamlGuy.TaskbarGroup.Core/ITaskbarButton.cs +++ b/TheXamlGuy.TaskbarGroup.Core/ITaskbarButton.cs @@ -2,7 +2,7 @@ { public interface ITaskbarButton : IDisposable { - TaskbarButtonBounds Bounds { get; } + Rect Rect { get; } string Name { get; } } diff --git a/TheXamlGuy.TaskbarGroup.Core/ITaskbarButtonShortcutMonitor.cs b/TheXamlGuy.TaskbarGroup.Core/ITaskbarButtonShortcutMonitor.cs new file mode 100644 index 0000000..cdd6215 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/ITaskbarButtonShortcutMonitor.cs @@ -0,0 +1,7 @@ +namespace TheXamlGuy.TaskbarGroup.Core +{ + public interface ITaskbarButtonShortcutMonitor : IInitializable + { + + } +} diff --git a/TheXamlGuy.TaskbarGroup.Core/ITaskbarList.cs b/TheXamlGuy.TaskbarGroup.Core/ITaskbarList.cs new file mode 100644 index 0000000..169d3ad --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/ITaskbarList.cs @@ -0,0 +1,8 @@ + +namespace TheXamlGuy.TaskbarGroup.Core +{ + public interface ITaskbarList + { + IntPtr GetHandle(); + } +} \ No newline at end of file diff --git a/TheXamlGuy.TaskbarGroup.Core/ITaskbarMonitor.cs b/TheXamlGuy.TaskbarGroup.Core/ITaskbarMonitor.cs deleted file mode 100644 index 9b3d4a5..0000000 --- a/TheXamlGuy.TaskbarGroup.Core/ITaskbarMonitor.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace TheXamlGuy.TaskbarGroup.Core -{ - public interface ITaskbarMonitor : IInitializable - { - - } -} \ No newline at end of file diff --git a/TheXamlGuy.TaskbarGroup.Core/PInvoke.cs b/TheXamlGuy.TaskbarGroup.Core/PInvoke.cs new file mode 100644 index 0000000..bdda4d4 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/PInvoke.cs @@ -0,0 +1,62 @@ +using System.Runtime.InteropServices; +using Windows.Win32.Foundation; + +namespace Windows.Win32 +{ + public static partial class PInvoke + { + public static readonly int SPI_SETWORKAREA = 0x002F; + public static readonly uint WM_TASKBARCREATED = PInvoke.RegisterWindowMessage("TaskbarCreated"); + + [StructLayout(LayoutKind.Sequential)] + public struct AppBarData + { + public uint cbSize; + public IntPtr hWnd; + public uint uCallbackMessage; + public AppBarEdge uEdge; + public RECT rect; + public int lParam; + } + + public enum AppBarMessage : uint + { + New = 0x00000000, + Remove = 0x00000001, + QueryPos = 0x00000002, + SetPos = 0x00000003, + GetState = 0x00000004, + GetTaskbarPos = 0x00000005, + Activate = 0x00000006, + GetAutoHideBar = 0x00000007, + SetAutoHideBar = 0x00000008, + WindowPosChanged = 0x00000009, + SetState = 0x0000000A, + } + + [DllImport("user32.dll", SetLastError = true)] + public static extern IntPtr DefWindowProcW(IntPtr handle, uint msg, IntPtr wParam, IntPtr lParam); + + [DllImport("shell32.dll", SetLastError = true)] + public static extern IntPtr SHAppBarMessage(AppBarMessage dwMessage, ref AppBarData pData); + + public static AppBarData GetAppBarData(IntPtr handle) + { + return new AppBarData + { + cbSize = (uint)Marshal.SizeOf(typeof(AppBarData)), + hWnd = handle + }; + } + + public enum AppBarEdge : uint + { + Left = 0, + Top = 1, + Right = 2, + Bottom = 3 + } + + public static void GetAppBarPosition(ref AppBarData appBarData) => SHAppBarMessage(AppBarMessage.GetTaskbarPos, ref appBarData); + } +} diff --git a/TheXamlGuy.TaskbarGroup.Core/RECTExtensions.cs b/TheXamlGuy.TaskbarGroup.Core/RECTExtensions.cs index 2550cd8..60740e9 100644 --- a/TheXamlGuy.TaskbarGroup.Core/RECTExtensions.cs +++ b/TheXamlGuy.TaskbarGroup.Core/RECTExtensions.cs @@ -5,10 +5,10 @@ namespace TheXamlGuy.TaskbarGroup.Core { internal static class RECTExtensions { - internal static Rect ToRect(this RECT rect) + internal static Windows.Foundation.Rect ToRect(this RECT rect) { - if (rect.right - rect.left < 0 || rect.bottom - rect.top < 0) return new Rect(rect.left, rect.top, 0, 0); - return new Rect(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top); + if (rect.right - rect.left < 0 || rect.bottom - rect.top < 0) return new Windows.Foundation.Rect(rect.left, rect.top, 0, 0); + return new Windows.Foundation.Rect(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top); } } } \ No newline at end of file diff --git a/TheXamlGuy.TaskbarGroup.Core/TaskbarButtonBounds.cs b/TheXamlGuy.TaskbarGroup.Core/Rect.cs similarity index 68% rename from TheXamlGuy.TaskbarGroup.Core/TaskbarButtonBounds.cs rename to TheXamlGuy.TaskbarGroup.Core/Rect.cs index abe12b5..7c1bfc8 100644 --- a/TheXamlGuy.TaskbarGroup.Core/TaskbarButtonBounds.cs +++ b/TheXamlGuy.TaskbarGroup.Core/Rect.cs @@ -1,13 +1,13 @@ namespace TheXamlGuy.TaskbarGroup.Core { - public record TaskbarButtonBounds + public record Rect { - public TaskbarButtonBounds() + public Rect() { } - public TaskbarButtonBounds(int x, int y, int width, int height) + public Rect(int x, int y, int width, int height) { X = x; Y = y; diff --git a/TheXamlGuy.TaskbarGroup.Core/Screen.cs b/TheXamlGuy.TaskbarGroup.Core/Screen.cs index 7480d9c..f287deb 100644 --- a/TheXamlGuy.TaskbarGroup.Core/Screen.cs +++ b/TheXamlGuy.TaskbarGroup.Core/Screen.cs @@ -32,7 +32,7 @@ namespace TheXamlGuy.TaskbarGroup.Core { var monitorData = GetMonitorData(monitorHandle); - Bounds = new Rect(monitorData.MonitorRect.left, monitorData.MonitorRect.top, monitorData.MonitorRect.right - monitorData.MonitorRect.left, monitorData.MonitorRect.bottom - monitorData.MonitorRect.top); + Bounds = new Windows.Foundation.Rect(monitorData.MonitorRect.left, monitorData.MonitorRect.top, monitorData.MonitorRect.right - monitorData.MonitorRect.left, monitorData.MonitorRect.bottom - monitorData.MonitorRect.top); Primary = (monitorData.Flags & (int)MonitorFlag.MONITOR_DEFAULTTOPRIMARY) != 0; DeviceName = monitorData.DeviceName; } @@ -47,13 +47,13 @@ namespace TheXamlGuy.TaskbarGroup.Core MONITOR_DEFAULTTONEAREST = 2 } - public Rect Bounds { get; } + public Windows.Foundation.Rect Bounds { get; } public string DeviceName { get; } public bool Primary { get; } - public Rect WorkingArea => GetWorkingArea(); + public Windows.Foundation.Rect WorkingArea => GetWorkingArea(); public static Screen FromHandle(IntPtr handle) { @@ -83,7 +83,7 @@ namespace TheXamlGuy.TaskbarGroup.Core return monitorData; } - private Rect GetWorkingArea() + private Windows.Foundation.Rect GetWorkingArea() { if (!_multiMonitorSupport || _monitorHandle == (IntPtr)PRIMARY_MONITOR) { @@ -91,7 +91,7 @@ namespace TheXamlGuy.TaskbarGroup.Core } var monitorData = GetMonitorData(_monitorHandle); - return new Rect(monitorData.WorkAreaRect.left, monitorData.WorkAreaRect.top, monitorData.WorkAreaRect.right - monitorData.WorkAreaRect.left, monitorData.WorkAreaRect.bottom - monitorData.WorkAreaRect.top); + return new Windows.Foundation.Rect(monitorData.WorkAreaRect.left, monitorData.WorkAreaRect.top, monitorData.WorkAreaRect.right - monitorData.WorkAreaRect.left, monitorData.WorkAreaRect.bottom - monitorData.WorkAreaRect.top); } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] diff --git a/TheXamlGuy.TaskbarGroup.Core/SystemInformationHelper.cs b/TheXamlGuy.TaskbarGroup.Core/SystemInformationHelper.cs index 9c87457..7099928 100644 --- a/TheXamlGuy.TaskbarGroup.Core/SystemInformationHelper.cs +++ b/TheXamlGuy.TaskbarGroup.Core/SystemInformationHelper.cs @@ -10,21 +10,21 @@ namespace TheXamlGuy.TaskbarGroup.Core { private const int SPI_GETWORKAREA = 48; - public static Rect VirtualScreen => GetVirtualScreen(); - public static Rect WorkingArea => GetWorkingArea(); + public static Windows.Foundation.Rect VirtualScreen => GetVirtualScreen(); + public static Windows.Foundation.Rect WorkingArea => GetWorkingArea(); - private static Rect GetVirtualScreen() + private static Windows.Foundation.Rect GetVirtualScreen() { var size = new Size(PInvoke.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CXSCREEN), PInvoke.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CYSCREEN)); - return new Rect(0, 0, size.Width, size.Height); + return new Windows.Foundation.Rect(0, 0, size.Width, size.Height); } - private static Rect GetWorkingArea() + private static Windows.Foundation.Rect GetWorkingArea() { var rect = new RECT(); SystemParametersInfo(SPI_GETWORKAREA, 0, ref rect, 0); - return new Rect(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top); + return new Windows.Foundation.Rect(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top); } [DllImport("user32.dll", CharSet = CharSet.Auto)] diff --git a/TheXamlGuy.TaskbarGroup.Core/TaskButtonShortcutCreated.cs b/TheXamlGuy.TaskbarGroup.Core/TaskButtonShortcutCreated.cs new file mode 100644 index 0000000..8ad26f8 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/TaskButtonShortcutCreated.cs @@ -0,0 +1,4 @@ +namespace TheXamlGuy.TaskbarGroup.Core +{ + public record TaskButtonShortcutCreated; +} diff --git a/TheXamlGuy.TaskbarGroup.Core/TaskButtonShortcutRemoved.cs b/TheXamlGuy.TaskbarGroup.Core/TaskButtonShortcutRemoved.cs new file mode 100644 index 0000000..a743d0d --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/TaskButtonShortcutRemoved.cs @@ -0,0 +1,4 @@ +namespace TheXamlGuy.TaskbarGroup.Core +{ + public record TaskButtonShortcutRemoved; +} diff --git a/TheXamlGuy.TaskbarGroup.Core/Taskbar.cs b/TheXamlGuy.TaskbarGroup.Core/Taskbar.cs index 39b591e..5658c1b 100644 --- a/TheXamlGuy.TaskbarGroup.Core/Taskbar.cs +++ b/TheXamlGuy.TaskbarGroup.Core/Taskbar.cs @@ -1,45 +1,37 @@ -using System.Runtime.InteropServices; -using Windows.Win32.Foundation; +using Windows.Win32; namespace TheXamlGuy.TaskbarGroup.Core { - public class Taskbar : ITaskbar + public partial class Taskbar : ITaskbar { - private const string ShellTrayHandleName = "Shell_TrayWnd"; + private readonly IDisposer disposer; + private readonly IMessenger messenger; + private bool isDrag; + private bool isWithinBounds; - private enum AppBarEdge : uint + public Taskbar(IMessenger messenger, + IDisposer disposer) { - Left = 0, - Top = 1, - Right = 2, - Bottom = 3 + this.messenger = messenger; + this.disposer = disposer; } - private enum AppBarMessage : uint + public void Dispose() { - New = 0x00000000, - Remove = 0x00000001, - QueryPos = 0x00000002, - SetPos = 0x00000003, - GetState = 0x00000004, - GetTaskbarPos = 0x00000005, - Activate = 0x00000006, - GetAutoHideBar = 0x00000007, - SetAutoHideBar = 0x00000008, - WindowPosChanged = 0x00000009, - SetState = 0x0000000A, + disposer.Dispose(this); + GC.SuppressFinalize(this); } public TaskbarState GetCurrentState() { - var handle = GetSystemTrayHandle(); + var handle = GetHandle(); var state = new TaskbarState { Screen = Screen.FromHandle(handle) }; - var appBarData = GetAppBarData(handle); - GetAppBarPosition(ref appBarData); + var appBarData = PInvoke.GetAppBarData(handle); + PInvoke.GetAppBarPosition(ref appBarData); state.Rect = appBarData.rect.ToRect(); state.Placement = (TaskbarPlacement)appBarData.uEdge; @@ -47,34 +39,77 @@ namespace TheXamlGuy.TaskbarGroup.Core return state; } - [DllImport("user32.dll", SetLastError = true)] - private static extern IntPtr DefWindowProcW(IntPtr handle, uint msg, IntPtr wParam, IntPtr lParam); - - private static IntPtr GetSystemTrayHandle() => WindowHelper.Find(ShellTrayHandleName); - - [DllImport("shell32.dll", SetLastError = true)] - private static extern IntPtr SHAppBarMessage(AppBarMessage dwMessage, ref AppBarData pData); - - private AppBarData GetAppBarData(IntPtr handle) + public IntPtr GetHandle() { - return new AppBarData - { - cbSize = (uint)Marshal.SizeOf(typeof(AppBarData)), - hWnd = handle - }; + return WindowHelper.Find("Shell_TrayWnd"); } - private void GetAppBarPosition(ref AppBarData appBarData) => SHAppBarMessage(AppBarMessage.GetTaskbarPos, ref appBarData); - - [StructLayout(LayoutKind.Sequential)] - private struct AppBarData + public void Initialize() { - public uint cbSize; - public IntPtr hWnd; - public uint uCallbackMessage; - public AppBarEdge uEdge; - public RECT rect; - public int lParam; + disposer.Add(this, messenger.Subscribe(OnWndProc)); + disposer.Add(this, messenger.Subscribe(OnPointerReleased)); + disposer.Add(this, messenger.Subscribe(OnPointerMoved)); + disposer.Add(this, messenger.Subscribe(OnPointerDrag)); + } + + private void OnPointerDrag(PointerDrag args) + { + if (isWithinBounds) + { + if (isDrag) + { + messenger.Send(); + } + else + { + messenger.Send(); + } + + isDrag = true; + } + else + { + isDrag = false; + } + } + + private void OnPointerMoved(PointerMoved args) + { + var taskbarHandle = GetHandle(); + if (WindowHelper.TryGetBounds(taskbarHandle, out var rect)) + { + if (args.Location.IsWithinBounds(rect)) + { + if (isWithinBounds) + { + return; + } + + isWithinBounds = true; + messenger.Send(); + } + else + { + isDrag = false; + isWithinBounds = false; + } + } + } + + private void OnPointerReleased(PointerReleased args) + { + if (isDrag) + { + isDrag = false; + } + } + + private void OnWndProc(WndProc args) + { + if (args.Message == PInvoke.WM_TASKBARCREATED || args.Message == (int)WndProcMessages.WM_SETTINGCHANGE && (int)args.WParam == PInvoke.SPI_SETWORKAREA) + { + messenger.Send(); + } } } } \ No newline at end of file diff --git a/TheXamlGuy.TaskbarGroup.Core/TaskbarButton.cs b/TheXamlGuy.TaskbarGroup.Core/TaskbarButton.cs index ebbac49..8195828 100644 --- a/TheXamlGuy.TaskbarGroup.Core/TaskbarButton.cs +++ b/TheXamlGuy.TaskbarGroup.Core/TaskbarButton.cs @@ -7,22 +7,22 @@ private bool isWithinBounds; private bool isDrag; - public TaskbarButton(IMessenger messenger, - IDisposer disposer, - string name, - TaskbarButtonBounds bounds) + public TaskbarButton(string name, + Rect rect, + IMessenger messenger, + IDisposer disposer) { this.messenger = messenger; this.disposer = disposer; Name = name; - Bounds = bounds; + Rect = rect; disposer.Add(this, messenger.Subscribe(OnPointerReleased)); disposer.Add(this, messenger.Subscribe(OnPointerMoved)); disposer.Add(this, messenger.Subscribe(OnPointerDrag)); } - public TaskbarButtonBounds Bounds { get; internal set; } + public Rect Rect { get; internal set; } public string Name { get; internal set; } @@ -32,21 +32,6 @@ GC.SuppressFinalize(this); } - private bool IsWithinBounds(PointerLocation args) - { - if (args.X >= Bounds.X - && args.X <= Bounds.X + Bounds.Width - && args.Y >= Bounds.Y - && args.Y <= Bounds.Y + Bounds.Height) - { - return true; - } - else - { - return false; - } - } - private void OnPointerDrag(PointerDrag args) { if (isWithinBounds) @@ -70,7 +55,7 @@ private void OnPointerMoved(PointerMoved args) { - if (IsWithinBounds(args.Location)) + if (args.Location.IsWithinBounds(Rect)) { if (isWithinBounds) { @@ -78,7 +63,7 @@ } isWithinBounds = true; - messenger.Send(new TaskbarButtonEntered(this)); + messenger.Send(new TaskbarButtonEnter(this)); } else { diff --git a/TheXamlGuy.TaskbarGroup.Core/TaskbarButtonConfiguration.cs b/TheXamlGuy.TaskbarGroup.Core/TaskbarButtonConfiguration.cs new file mode 100644 index 0000000..40ec1ee --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/TaskbarButtonConfiguration.cs @@ -0,0 +1,7 @@ +namespace TheXamlGuy.TaskbarGroup.Core +{ + public class TaskbarButtonConfiguration + { + public string? PinnedShortcutDirectory { get; set; } + } +} diff --git a/TheXamlGuy.TaskbarGroup.Core/TaskbarButtonEnter.cs b/TheXamlGuy.TaskbarGroup.Core/TaskbarButtonEnter.cs new file mode 100644 index 0000000..5114865 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/TaskbarButtonEnter.cs @@ -0,0 +1,4 @@ +namespace TheXamlGuy.TaskbarGroup.Core +{ + public record TaskbarButtonEnter(TaskbarButton Button); +} diff --git a/TheXamlGuy.TaskbarGroup.Core/TaskbarButtonEntered.cs b/TheXamlGuy.TaskbarGroup.Core/TaskbarButtonEntered.cs deleted file mode 100644 index 5a7158b..0000000 --- a/TheXamlGuy.TaskbarGroup.Core/TaskbarButtonEntered.cs +++ /dev/null @@ -1,4 +0,0 @@ -namespace TheXamlGuy.TaskbarGroup.Core -{ - public record TaskbarButtonEntered(TaskbarButton Button); -} diff --git a/TheXamlGuy.TaskbarGroup.Core/TaskbarButtonMonitor.cs b/TheXamlGuy.TaskbarGroup.Core/TaskbarButtonMonitor.cs index 25e5998..d80d105 100644 --- a/TheXamlGuy.TaskbarGroup.Core/TaskbarButtonMonitor.cs +++ b/TheXamlGuy.TaskbarGroup.Core/TaskbarButtonMonitor.cs @@ -4,27 +4,34 @@ using System.Diagnostics; namespace TheXamlGuy.TaskbarGroup.Core { + public class TaskbarButtonMonitor : ITaskbarButtonMonitor { private readonly IDispatcherTimer dispatcherTimer; private readonly IDispatcherTimerFactory dispatcherTimerFactory; - private readonly IServiceFactory serviceFactory; + private readonly ITaskbarList taskbarList; private readonly IMessenger messenger; + private readonly IServiceFactory serviceFactory; + private readonly IDisposer disposer; private readonly Dictionary taskbarButtons = new(); - private RECT taskbarBoundsCache; + private Rect? taskbarRectCache; private IUIAutomationCondition? taskListCondition; private IUIAutomationElement? taskListElement; - private HWND taskListHandle; + private IntPtr taskListHandle; - public TaskbarButtonMonitor(IMessenger messenger, + public TaskbarButtonMonitor(ITaskbarList taskbarList, + IMessenger messenger, IDispatcherTimerFactory dispatcherTimerFactory, - IServiceFactory serviceFactory) + IServiceFactory serviceFactory, + IDisposer disposer) { + this.taskbarList = taskbarList; this.messenger = messenger; this.dispatcherTimerFactory = dispatcherTimerFactory; this.serviceFactory = serviceFactory; + this.disposer = disposer; - dispatcherTimer = dispatcherTimerFactory.Create(OnDispatcher, TimeSpan.FromMilliseconds(500)); + disposer.Add(this, dispatcherTimer = dispatcherTimerFactory.Create(OnDispatcher, TimeSpan.FromMilliseconds(500))); } public void Initialize() @@ -32,17 +39,12 @@ namespace TheXamlGuy.TaskbarGroup.Core var clientUIAutomation = new CUIAutomation(); taskListCondition = clientUIAutomation.CreateTrueCondition(); - var trayHandle = WindowHelper.Find("Shell_TrayWnd"); - - var rebarHandle = WindowHelper.Find("ReBarWindow32", trayHandle); - var taskHandle = WindowHelper.Find("MSTaskSwWClass", rebarHandle); - taskListHandle = WindowHelper.Find("MSTaskListWClass", taskHandle); - + taskListHandle = taskbarList.GetHandle(); taskListElement = clientUIAutomation.ElementFromHandle(taskListHandle); - if (WindowHelper.TryGetBounds(taskListHandle, out var bounds)) + if (WindowHelper.TryGetBounds(taskListHandle, out var rect)) { - taskbarBoundsCache = bounds; + taskbarRectCache = rect; } dispatcherTimer.Start(); @@ -51,17 +53,11 @@ namespace TheXamlGuy.TaskbarGroup.Core private bool CheckDirtyTaskbarRegion() { - if (WindowHelper.TryGetBounds(taskListHandle, out var bounds)) + if (WindowHelper.TryGetBounds(taskListHandle, out var rect)) { - var width = taskbarBoundsCache.right - taskbarBoundsCache.left; - var height = taskbarBoundsCache.bottom - taskbarBoundsCache.top; - - var deltaWidth = bounds.right - bounds.left; - var deltaHeight = bounds.bottom - bounds.top; - - if (width != deltaWidth || height != deltaHeight) + if (taskbarRectCache?.Width != rect.Width || taskbarRectCache?.Height != rect.Height) { - taskbarBoundsCache = bounds; + taskbarRectCache = rect; return true; } } @@ -76,7 +72,7 @@ namespace TheXamlGuy.TaskbarGroup.Core var buttons = new Dictionary(); if (taskElements is not null) { - for (int index = 0; index <= taskElements.Length - 1; index++) + for (var index = 0; index <= taskElements.Length - 1; index++) { var taskUIElement = taskElements.GetElement(index); var name = taskUIElement.CurrentName; @@ -128,7 +124,7 @@ namespace TheXamlGuy.TaskbarGroup.Core var name = button.Key; var bounds = button.Value; - var buttonBounds = new TaskbarButtonBounds(bounds.left, + var rect = new Rect(bounds.left, bounds.top, bounds.right - bounds.left, bounds.bottom - bounds.top); @@ -137,14 +133,14 @@ namespace TheXamlGuy.TaskbarGroup.Core { Debug.WriteLine($"{name} button updated"); - taskbarButtons[name].Bounds = buttonBounds; + taskbarButtons[name].Rect = rect; messenger.Send(new TaskbarButtonUpdated(taskbarButtons[name])); } else { Debug.WriteLine($"{name} button added"); - taskbarButtons.Add(name, serviceFactory.Create(name, buttonBounds)); + taskbarButtons.Add(name, serviceFactory.Create(name, rect)); messenger.Send(new TaskbarButtonCreated(taskbarButtons[name])); } } diff --git a/TheXamlGuy.TaskbarGroup.Core/TaskbarButtonShortcutMonitor.cs b/TheXamlGuy.TaskbarGroup.Core/TaskbarButtonShortcutMonitor.cs new file mode 100644 index 0000000..5fe93b2 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/TaskbarButtonShortcutMonitor.cs @@ -0,0 +1,41 @@ +namespace TheXamlGuy.TaskbarGroup.Core +{ + public class TaskbarButtonShortcutMonitor : ITaskbarButtonShortcutMonitor + { + private readonly IMessenger messenger; + private FileSystemWatcher? _watcher; + private readonly TaskbarButtonConfiguration configuration; + + public TaskbarButtonShortcutMonitor( + IMessenger messenger) + { + this.messenger = messenger; + } + + public void Initialize() + { + //_watcher = new FileSystemWatcher(configuration.PinnedShortcutDirectory) + //{ + // NotifyFilter = NotifyFilters.FileName, + // Filter = "*.ink", + // IncludeSubdirectories = true, + // EnableRaisingEvents = true + //}; + + //_watcher.Changed += OnChanged; + } + + private void OnChanged(object sender, FileSystemEventArgs args) + { + if (args.ChangeType is WatcherChangeTypes.Created) + { + messenger.Send(); + } + + if (args.ChangeType is WatcherChangeTypes.Deleted) + { + messenger.Send(); + } + } + } +} diff --git a/TheXamlGuy.TaskbarGroup.Core/TaskbarDragEnter.cs b/TheXamlGuy.TaskbarGroup.Core/TaskbarDragEnter.cs new file mode 100644 index 0000000..c93492f --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/TaskbarDragEnter.cs @@ -0,0 +1,5 @@ +namespace TheXamlGuy.TaskbarGroup.Core +{ + public record TaskbarDragEnter(); + +} diff --git a/TheXamlGuy.TaskbarGroup.Core/TaskbarDragOver.cs b/TheXamlGuy.TaskbarGroup.Core/TaskbarDragOver.cs new file mode 100644 index 0000000..05c732c --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/TaskbarDragOver.cs @@ -0,0 +1,4 @@ +namespace TheXamlGuy.TaskbarGroup.Core +{ + public record TaskbarDragOver(); +} diff --git a/TheXamlGuy.TaskbarGroup.Core/TaskbarEnter.cs b/TheXamlGuy.TaskbarGroup.Core/TaskbarEnter.cs new file mode 100644 index 0000000..7850cb9 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/TaskbarEnter.cs @@ -0,0 +1,5 @@ +namespace TheXamlGuy.TaskbarGroup.Core +{ + public record TaskbarEnter(); + +} diff --git a/TheXamlGuy.TaskbarGroup.Core/TaskbarList.cs b/TheXamlGuy.TaskbarGroup.Core/TaskbarList.cs new file mode 100644 index 0000000..d597d5b --- /dev/null +++ b/TheXamlGuy.TaskbarGroup.Core/TaskbarList.cs @@ -0,0 +1,21 @@ +namespace TheXamlGuy.TaskbarGroup.Core +{ + public class TaskbarList : ITaskbarList + { + private readonly ITaskbar taskbar; + + public TaskbarList(ITaskbar taskbar) + { + this.taskbar = taskbar; + } + + public IntPtr GetHandle() + { + var trayHandle = taskbar.GetHandle(); + + var rebarHandle = WindowHelper.Find("ReBarWindow32", trayHandle); + var taskHandle = WindowHelper.Find("MSTaskSwWClass", rebarHandle); + return WindowHelper.Find("MSTaskListWClass", taskHandle); + } + } +} diff --git a/TheXamlGuy.TaskbarGroup.Core/TaskbarMonitor.cs b/TheXamlGuy.TaskbarGroup.Core/TaskbarMonitor.cs deleted file mode 100644 index 99e7242..0000000 --- a/TheXamlGuy.TaskbarGroup.Core/TaskbarMonitor.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Windows.Win32; - -namespace TheXamlGuy.TaskbarGroup.Core -{ - public class TaskbarMonitor : ITaskbarMonitor - { - private const int SPI_SETWORKAREA = 0x002F; - - private readonly uint WM_TASKBARCREATED = PInvoke.RegisterWindowMessage("TaskbarCreated"); - private readonly IMessenger messenger; - - public TaskbarMonitor(IMessenger messenger) - { - this.messenger = messenger; - } - - public void Initialize() - { - messenger.Subscribe(OnWndProc); - } - - private void OnWndProc(WndProc args) - { - if (args.Message == WM_TASKBARCREATED || args.Message == (int)WndProcMessages.WM_SETTINGCHANGE && (int)args.WParam == SPI_SETWORKAREA) - { - messenger.Send(); - } - } - } -} \ No newline at end of file diff --git a/TheXamlGuy.TaskbarGroup.Core/TaskbarState.cs b/TheXamlGuy.TaskbarGroup.Core/TaskbarState.cs index 85845d8..2310a41 100644 --- a/TheXamlGuy.TaskbarGroup.Core/TaskbarState.cs +++ b/TheXamlGuy.TaskbarGroup.Core/TaskbarState.cs @@ -5,7 +5,7 @@ namespace TheXamlGuy.TaskbarGroup.Core public struct TaskbarState { public TaskbarPlacement Placement; - public Rect Rect; + public Windows.Foundation.Rect Rect; public Screen Screen; } } \ No newline at end of file diff --git a/TheXamlGuy.TaskbarGroup.Core/TheXamlGuy.TaskbarGroup.Core.csproj b/TheXamlGuy.TaskbarGroup.Core/TheXamlGuy.TaskbarGroup.Core.csproj index 0468f29..6cfce7d 100644 --- a/TheXamlGuy.TaskbarGroup.Core/TheXamlGuy.TaskbarGroup.Core.csproj +++ b/TheXamlGuy.TaskbarGroup.Core/TheXamlGuy.TaskbarGroup.Core.csproj @@ -9,9 +9,10 @@ + + - all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/TheXamlGuy.TaskbarGroup.Core/WindowHelper.cs b/TheXamlGuy.TaskbarGroup.Core/WindowHelper.cs index 0df9f1d..307dece 100644 --- a/TheXamlGuy.TaskbarGroup.Core/WindowHelper.cs +++ b/TheXamlGuy.TaskbarGroup.Core/WindowHelper.cs @@ -1,4 +1,5 @@ -using Windows.Win32; +using System.Diagnostics.CodeAnalysis; +using Windows.Win32; using Windows.Win32.Foundation; using Windows.Win32.UI.WindowsAndMessaging; @@ -6,27 +7,42 @@ namespace TheXamlGuy.TaskbarGroup.Core { public class WindowHelper { - public static void MoveAndResize(HWND handle, int x, int y, int width, int height) - { - PInvoke.SetWindowPos(handle, new HWND(), x, y, width, height, 0); - } - public static void BringToForeground(HWND handle) { - if (TryGetBounds(handle, out var bounds)) + if (TryGetBoundsUnsafe(handle, out var bounds)) { PInvoke.SetWindowPos(handle, new HWND(), bounds.left, bounds.top, bounds.right - bounds.left, bounds.bottom - bounds.top, SET_WINDOW_POS_FLAGS.SWP_SHOWWINDOW | SET_WINDOW_POS_FLAGS.SWP_NOACTIVATE); } } - public static HWND Find(string windowName) => PInvoke.FindWindow(windowName, null); - - public static HWND Find(string windowName, HWND parentHandle) + public static IntPtr Find(string windowName) { - return PInvoke.FindWindowEx(parentHandle, new HWND(), windowName, null); + return PInvoke.FindWindow(windowName, null); } - public static unsafe bool TryGetBounds(IntPtr handle, out RECT rect) + public static IntPtr Find(string windowName, IntPtr parentHandle) + { + return PInvoke.FindWindowEx(new HWND(parentHandle), new HWND(), windowName, null); + } + + public static void MoveAndResize(HWND handle, int x, int y, int width, int height) + { + PInvoke.SetWindowPos(handle, new HWND(), x, y, width, height, 0); + } + public static bool TryGetBounds(IntPtr handle, [MaybeNullWhen(false)]out Rect rect) + { + if (TryGetBoundsUnsafe(handle, out var unsafeRect)) + { + rect = new Rect(unsafeRect.left, unsafeRect.top, unsafeRect.right - unsafeRect.left, unsafeRect.bottom - unsafeRect.top); + + return true; + } + + rect = null; + return false; + } + + private static unsafe bool TryGetBoundsUnsafe(IntPtr handle, out RECT rect) { fixed (RECT* lpRectLocal = &rect) { diff --git a/TheXamlGuy.TaskbarGroup/App.xaml.cs b/TheXamlGuy.TaskbarGroup/App.xaml.cs index 6381db1..779f845 100644 --- a/TheXamlGuy.TaskbarGroup/App.xaml.cs +++ b/TheXamlGuy.TaskbarGroup/App.xaml.cs @@ -1,8 +1,7 @@ -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using System; using System.IO; -using System.Reflection; using System.Windows; using TheXamlGuy.TaskbarGroup.Core; using TheXamlGuy.TaskbarGroup.Flyout; @@ -17,15 +16,11 @@ namespace TheXamlGuy.TaskbarGroup protected override async void OnStartup(StartupEventArgs args) { - var appLocation = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location); - - host = Host.CreateDefaultBuilder() - .ConfigureAppConfiguration(config => - { - config.SetBasePath(appLocation); - }) - .ConfigureServices(ConfigureServices) - .Build(); + host = new HostBuilder() + .UseContentRoot(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), + "TheXamlGuy", "TaskbarGroup"), true) + .ConfigureServices(ConfigureServices) + .Build(); await host.StartAsync(); } @@ -36,6 +31,7 @@ namespace TheXamlGuy.TaskbarGroup .AddRequiredCore() .AddRequiredFoundation() .AddRequiredFlyoutFoundation() + .AddHandler() .AddHandler() .AddSingleton() .AddTransient() diff --git a/TheXamlGuy.TaskbarGroup/LifeCycles/ApplicationHost.cs b/TheXamlGuy.TaskbarGroup/LifeCycles/ApplicationHost.cs index d5a0a5d..9583b2c 100644 --- a/TheXamlGuy.TaskbarGroup/LifeCycles/ApplicationHost.cs +++ b/TheXamlGuy.TaskbarGroup/LifeCycles/ApplicationHost.cs @@ -8,15 +8,24 @@ namespace TheXamlGuy.TaskbarGroup { public sealed class ApplicationHost : IHostedService { - private readonly TaskbarButtonFlyoutWindow flyoutWindow; - private readonly IEnumerable initializables; + private readonly IMediator mediator; + private readonly List initializables = new(); private bool isInitialized; - public ApplicationHost(IEnumerable initializables, - TaskbarButtonFlyoutWindow flyoutWindow) + public ApplicationHost(IWndProcMonitor wndProcMonitor, + ITaskbar taskbar, + IPointerMonitor pointerMonitor, + ITaskbarButtonMonitor taskbarButtonMonitor, + ITaskbarButtonShortcutMonitor taskbarButtonShortcutMonitor, + IMediator mediator) { - this.initializables = initializables; - this.flyoutWindow = flyoutWindow; + initializables.Add(wndProcMonitor); + initializables.Add(taskbar); + initializables.Add(pointerMonitor); + initializables.Add(taskbarButtonMonitor); + initializables.Add(taskbarButtonShortcutMonitor); + + this.mediator = mediator; } public async Task StartAsync(CancellationToken cancellationToken) @@ -27,7 +36,6 @@ namespace TheXamlGuy.TaskbarGroup isInitialized = true; } - public async Task StopAsync(CancellationToken cancellationToken) { await Task.CompletedTask; @@ -50,7 +58,7 @@ namespace TheXamlGuy.TaskbarGroup { if (!isInitialized) { - flyoutWindow.Show(); + mediator.Handle(); await Task.CompletedTask; } } diff --git a/TheXamlGuy.TaskbarGroup/LifeCycles/TaskbarButtonFlyoutActivationHandler.cs b/TheXamlGuy.TaskbarGroup/LifeCycles/TaskbarButtonFlyoutActivationHandler.cs index 2fb5778..14889cf 100644 --- a/TheXamlGuy.TaskbarGroup/LifeCycles/TaskbarButtonFlyoutActivationHandler.cs +++ b/TheXamlGuy.TaskbarGroup/LifeCycles/TaskbarButtonFlyoutActivationHandler.cs @@ -49,8 +49,8 @@ namespace TheXamlGuy.TaskbarGroup case TaskbarPlacement.Bottom: placement = TaskbarButtonFlyoutPlacement.Bottom; - window.Left = ((button.Bounds.X + (button.Bounds.Width / 2)) / dpiX) - (window.Width / 2); - window.Top = (button.Bounds.Y / dpiY) - window.Height; + window.Left = ((button.Rect.X + (button.Rect.Width / 2)) / dpiX) - (window.Width / 2); + window.Top = (button.Rect.Y / dpiY) - window.Height; break; } diff --git a/TheXamlGuy.TaskbarGroup/LifeCycles/TaskbarButtonFlyoutWindowActivation.cs b/TheXamlGuy.TaskbarGroup/LifeCycles/TaskbarButtonFlyoutWindowActivation.cs new file mode 100644 index 0000000..77eaf52 --- /dev/null +++ b/TheXamlGuy.TaskbarGroup/LifeCycles/TaskbarButtonFlyoutWindowActivation.cs @@ -0,0 +1,4 @@ +namespace TheXamlGuy.TaskbarGroup +{ + public record TaskbarButtonFlyoutWindowActivation(); +} diff --git a/TheXamlGuy.TaskbarGroup/LifeCycles/TaskbarButtonFlyoutWindowActivationHandler.cs b/TheXamlGuy.TaskbarGroup/LifeCycles/TaskbarButtonFlyoutWindowActivationHandler.cs new file mode 100644 index 0000000..268f84f --- /dev/null +++ b/TheXamlGuy.TaskbarGroup/LifeCycles/TaskbarButtonFlyoutWindowActivationHandler.cs @@ -0,0 +1,19 @@ +using TheXamlGuy.TaskbarGroup.Core; + +namespace TheXamlGuy.TaskbarGroup +{ + public class TaskbarButtonFlyoutWindowActivationHandler : IMessageHandler + { + private readonly TaskbarButtonFlyoutWindow flyoutWindow; + + public TaskbarButtonFlyoutWindowActivationHandler(TaskbarButtonFlyoutWindow flyoutWindow) + { + this.flyoutWindow = flyoutWindow; + } + + public void Handle(TaskbarButtonFlyoutWindowActivation message) + { + flyoutWindow.Show(); + } + } +} diff --git a/TheXamlGuy.TaskbarGroup/Program.cs b/TheXamlGuy.TaskbarGroup/Program.cs index a3980dd..c5c08f0 100644 --- a/TheXamlGuy.TaskbarGroup/Program.cs +++ b/TheXamlGuy.TaskbarGroup/Program.cs @@ -26,3 +26,4 @@ namespace TheXamlGuy.TaskbarGroup } } } + diff --git a/TheXamlGuy.TaskbarGroup/Windows/CreateTaskbarButtonGroupWindow.cs b/TheXamlGuy.TaskbarGroup/Windows/CreateTaskbarButtonGroupWindow.cs new file mode 100644 index 0000000..727005d --- /dev/null +++ b/TheXamlGuy.TaskbarGroup/Windows/CreateTaskbarButtonGroupWindow.cs @@ -0,0 +1,18 @@ +using System.Windows; +using System.Windows.Media; + +namespace TheXamlGuy.TaskbarGroup +{ + public class CreateTaskbarButtonGroupWindow : Window + { + public CreateTaskbarButtonGroupWindow() + { + Height = 0; + Width = 0; + WindowStyle = WindowStyle.None; + ResizeMode = ResizeMode.NoResize; + AllowsTransparency = true; + Background = new SolidColorBrush(Colors.Transparent); + } + } +} diff --git a/TheXamlGuy.TaskbarGroup/Windows/TaskbarButtonFlyoutWindow.cs b/TheXamlGuy.TaskbarGroup/Windows/TaskbarButtonFlyoutWindow.cs index 9fb43e7..839c6c7 100644 --- a/TheXamlGuy.TaskbarGroup/Windows/TaskbarButtonFlyoutWindow.cs +++ b/TheXamlGuy.TaskbarGroup/Windows/TaskbarButtonFlyoutWindow.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Windows.Threading; using TheXamlGuy.TaskbarGroup.Core; using TheXamlGuy.TaskbarGroup.Flyout.Controls; @@ -23,10 +24,16 @@ namespace TheXamlGuy.TaskbarGroup Hide(); }), DispatcherPriority.ContextIdle, null); + messenger.Subscribe(OnTaskbarDragEnter); messenger.Subscribe(OnTaskbarButtonInvoked); messenger.Subscribe(OnTaskbarButtonDragEnter); } + private void OnTaskbarDragEnter(TaskbarDragEnter obj) + { + Debug.WriteLine("fff"); + } + protected override void OnDeactivated(EventArgs args) { if (XamlContent is TaskbarButtonFlyout flyout) @@ -52,6 +59,5 @@ namespace TheXamlGuy.TaskbarGroup { Dispatcher.Invoke(() => Open(args.Button)); } - } } diff --git a/TheXamlGuy.TaskbarGroup/Windows/TransparentXamlWindow.cs b/TheXamlGuy.TaskbarGroup/Windows/TransparentXamlWindow.cs index 7485033..1443892 100644 --- a/TheXamlGuy.TaskbarGroup/Windows/TransparentXamlWindow.cs +++ b/TheXamlGuy.TaskbarGroup/Windows/TransparentXamlWindow.cs @@ -6,14 +6,7 @@ namespace TheXamlGuy.TaskbarGroup { public class TransparentXamlWindow : XamlWindow where TXamlContent : Windows.UI.Xaml.UIElement { - public TransparentXamlWindow() => PrepareDefaultWindow(); - - protected override WindowsXamlHost OnInitializing(WindowsXamlHost xamlHost) - { - return base.OnInitializing(xamlHost); - } - - private void PrepareDefaultWindow() + public TransparentXamlWindow() { ShowInTaskbar = false; WindowStyle = WindowStyle.None; @@ -21,5 +14,10 @@ namespace TheXamlGuy.TaskbarGroup AllowsTransparency = true; Background = new SolidColorBrush(Colors.Transparent); } + + protected override WindowsXamlHost OnInitializing(WindowsXamlHost xamlHost) + { + return base.OnInitializing(xamlHost); + } } }