create a setup builder impl from MicaSetup

This commit is contained in:
ema
2023-12-04 01:36:02 +08:00
parent edc597a19a
commit a6891e29dc
465 changed files with 34946 additions and 75 deletions

View File

@@ -0,0 +1,90 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace MicaSetup.Shell.Dialogs;
internal class ChangeNotifyEventManager
{
private static readonly ShellObjectChangeTypes[] _changeOrder = {
ShellObjectChangeTypes.ItemCreate,
ShellObjectChangeTypes.ItemRename,
ShellObjectChangeTypes.ItemDelete,
ShellObjectChangeTypes.AttributesChange,
ShellObjectChangeTypes.DirectoryCreate,
ShellObjectChangeTypes.DirectoryDelete,
ShellObjectChangeTypes.DirectoryContentsUpdate,
ShellObjectChangeTypes.DirectoryRename,
ShellObjectChangeTypes.Update,
ShellObjectChangeTypes.MediaInsert,
ShellObjectChangeTypes.MediaRemove,
ShellObjectChangeTypes.DriveAdd,
ShellObjectChangeTypes.DriveRemove,
ShellObjectChangeTypes.NetShare,
ShellObjectChangeTypes.NetUnshare,
ShellObjectChangeTypes.ServerDisconnect,
ShellObjectChangeTypes.SystemImageUpdate,
ShellObjectChangeTypes.AssociationChange,
ShellObjectChangeTypes.FreeSpace,
ShellObjectChangeTypes.DiskEventsMask,
ShellObjectChangeTypes.GlobalEventsMask,
ShellObjectChangeTypes.AllEventsMask
};
private readonly Dictionary<ShellObjectChangeTypes, Delegate> _events = new();
public void Register(ShellObjectChangeTypes changeType, Delegate handler)
{
if (!_events.TryGetValue(changeType, out var del))
{
_events.Add(changeType, handler);
}
else
{
del = MulticastDelegate.Combine(del, handler);
_events[changeType] = del;
}
}
public void Unregister(ShellObjectChangeTypes changeType, Delegate handler)
{
if (_events.TryGetValue(changeType, out var del))
{
del = MulticastDelegate.Remove(del, handler);
if (del == null)
{
_events.Remove(changeType);
}
else
{
_events[changeType] = del;
}
}
}
public void UnregisterAll() => _events.Clear();
public void Invoke(object sender, ShellObjectChangeTypes changeType, EventArgs args)
{
changeType &= ~ShellObjectChangeTypes.FromInterrupt;
foreach (var change in _changeOrder.Where(x => (x & changeType) != 0))
{
if (_events.TryGetValue(change, out var del))
{
del.DynamicInvoke(sender, args);
}
}
}
public ShellObjectChangeTypes RegisteredTypes => _events.Keys.Aggregate<ShellObjectChangeTypes, ShellObjectChangeTypes>(
ShellObjectChangeTypes.None,
(accumulator, changeType) => (changeType | accumulator));
}

View File

@@ -0,0 +1,71 @@
using System;
using System.Diagnostics;
namespace MicaSetup.Shell.Dialogs;
#pragma warning disable CS8618
internal class ChangeNotifyLock
{
private readonly uint _event = 0;
internal ChangeNotifyLock(Message message)
{
var lockId = Shell32.SHChangeNotification_Lock(
message.WParam, (int)message.LParam, out var pidl, out _event);
try
{
Trace.TraceInformation("Message: {0}", (ShellObjectChangeTypes)_event);
var notifyStruct = pidl.MarshalAs<ShellNotifyStruct>();
var guid = new Guid(ShellIIDGuid.IShellItem2);
if (notifyStruct.item1 != 0 &&
(((ShellObjectChangeTypes)_event) & ShellObjectChangeTypes.SystemImageUpdate) == ShellObjectChangeTypes.None)
{
if (CoreErrorHelper.Succeeded(Shell32.SHCreateItemFromIDList(
notifyStruct.item1, ref guid, out var nativeShellItem)))
{
nativeShellItem.GetDisplayName(ShellItemDesignNameOptions.FileSystemPath,
out var name);
ItemName = name;
Trace.TraceInformation("Item1: {0}", ItemName);
}
}
else
{
ImageIndex = (int)notifyStruct.item1;
}
if (notifyStruct.item2 != 0)
{
if (CoreErrorHelper.Succeeded(Shell32.SHCreateItemFromIDList(
notifyStruct.item2, ref guid, out var nativeShellItem)))
{
nativeShellItem.GetDisplayName(ShellItemDesignNameOptions.FileSystemPath,
out var name);
ItemName2 = name;
Trace.TraceInformation("Item2: {0}", ItemName2);
}
}
}
finally
{
if (lockId != 0)
{
Shell32.SHChangeNotification_Unlock(lockId);
}
}
}
public bool FromSystemInterrupt => ((ShellObjectChangeTypes)_event & ShellObjectChangeTypes.FromInterrupt)
!= ShellObjectChangeTypes.None;
public int ImageIndex { get; private set; }
public string ItemName { get; private set; }
public string ItemName2 { get; private set; }
public ShellObjectChangeTypes ChangeType => (ShellObjectChangeTypes)_event;
}

View File

@@ -0,0 +1,204 @@
using MicaSetup.Natives;
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading;
using static MicaSetup.Natives.User32;
namespace MicaSetup.Shell.Dialogs;
#pragma warning disable CS8618
internal class MessageListener : IDisposable
{
public const uint CreateWindowMessage = (uint)WindowMessage.WM_USER + 1;
public const uint DestroyWindowMessage = (uint)WindowMessage.WM_USER + 2;
public const uint BaseUserMessage = (uint)WindowMessage.WM_USER + 5;
private const string MessageWindowClassName = "MessageListenerClass";
private static readonly object _threadlock = new();
private static uint _atom;
private static Thread _windowThread = null!;
private static volatile bool _running = false;
private static readonly ShellObjectWatcherNativeMethods.WndProcDelegate wndProc = WndProc;
private static readonly Dictionary<IntPtr, MessageListener> _listeners = new();
private static nint _firstWindowHandle = 0;
private static readonly object _crossThreadWindowLock = new();
private static nint _tempHandle = 0;
public event EventHandler<WindowMessageEventArgs> MessageReceived;
public MessageListener()
{
lock (_threadlock)
{
if (_windowThread == null)
{
_windowThread = new Thread(ThreadMethod);
_windowThread.SetApartmentState(ApartmentState.STA);
_windowThread.Name = "ShellObjectWatcherMessageListenerHelperThread";
lock (_crossThreadWindowLock)
{
_windowThread.Start();
Monitor.Wait(_crossThreadWindowLock);
}
_firstWindowHandle = WindowHandle;
}
else
{
CrossThreadCreateWindow();
}
if (WindowHandle == 0)
{
throw new ShellException(LocalizedMessages.MessageListenerCannotCreateWindow,
Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error()));
}
_listeners.Add(WindowHandle, this);
}
}
private void CrossThreadCreateWindow()
{
if (_firstWindowHandle == 0)
{
throw new InvalidOperationException(LocalizedMessages.MessageListenerNoWindowHandle);
}
lock (_crossThreadWindowLock)
{
User32.PostMessage(_firstWindowHandle, (int)CreateWindowMessage, 0, 0);
Monitor.Wait(_crossThreadWindowLock);
}
WindowHandle = _tempHandle;
}
private static void RegisterWindowClass()
{
WindowClassEx classEx = new()
{
ClassName = MessageWindowClassName,
WndProc = wndProc,
Size = (uint)Marshal.SizeOf(typeof(WindowClassEx)),
};
var atom = ShellObjectWatcherNativeMethods.RegisterClassEx(ref classEx);
if (atom == 0)
{
throw new ShellException(LocalizedMessages.MessageListenerClassNotRegistered,
Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error()));
}
_atom = atom;
}
private static nint CreateWindow()
{
nint handle = ShellObjectWatcherNativeMethods.CreateWindowEx(
0,
MessageWindowClassName,
"MessageListenerWindow",
0,
0, 0, 0, 0,
new IntPtr(-3),
0, 0, 0);
return handle;
}
private void ThreadMethod()
{
lock (_crossThreadWindowLock)
{
_running = true;
if (_atom == 0)
{
RegisterWindowClass();
}
WindowHandle = CreateWindow();
Monitor.Pulse(_crossThreadWindowLock);
}
while (_running)
{
if (ShellObjectWatcherNativeMethods.GetMessage(out Message msg, 0, 0, 0))
{
ShellObjectWatcherNativeMethods.DispatchMessage(ref msg);
}
}
}
private static int WndProc(nint hwnd, uint msg, nint wparam, nint lparam)
{
switch (msg)
{
case CreateWindowMessage:
lock (_crossThreadWindowLock)
{
_tempHandle = CreateWindow();
Monitor.Pulse(_crossThreadWindowLock);
}
break;
case (uint)WindowMessage.WM_DESTROY:
_running = false;
break;
default:
MessageListener listener;
if (_listeners.TryGetValue(hwnd, out listener))
{
Message message = new(hwnd, msg, wparam, lparam, 0, new POINT());
listener.MessageReceived.SafeRaise(listener, new WindowMessageEventArgs(message));
}
break;
}
return ShellObjectWatcherNativeMethods.DefWindowProc(hwnd, msg, wparam, lparam);
}
public nint WindowHandle { get; private set; }
public static bool Running
{ get { return _running; } }
~MessageListener()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
lock (_threadlock)
{
_listeners.Remove(WindowHandle);
if (_listeners.Count == 0)
{
User32.PostMessage(WindowHandle, (int)WindowMessage.WM_DESTROY, 0, 0);
}
}
}
}
}
public class WindowMessageEventArgs : EventArgs
{
public Message Message { get; private set; }
internal WindowMessageEventArgs(Message msg) => Message = msg;
}

View File

@@ -0,0 +1,110 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace MicaSetup.Shell.Dialogs;
internal static class MessageListenerFilter
{
private static readonly object _registerLock = new();
private static readonly List<RegisteredListener> _packages = new();
public static MessageListenerFilterRegistrationResult Register(Action<WindowMessageEventArgs> callback)
{
lock (_registerLock)
{
uint message = 0;
var package = _packages.FirstOrDefault(x => x.TryRegister(callback, out message));
if (package == null)
{
package = new RegisteredListener();
if (!package.TryRegister(callback, out message))
{
throw new ShellException(LocalizedMessages.MessageListenerFilterUnableToRegister);
}
_packages.Add(package);
}
return new MessageListenerFilterRegistrationResult(
package.Listener.WindowHandle,
message);
}
}
public static void Unregister(nint listenerHandle, uint message)
{
lock (_registerLock)
{
var package = _packages.FirstOrDefault(x => x.Listener.WindowHandle == listenerHandle);
if (package == null || !package.Callbacks.Remove(message))
{
throw new ArgumentException(LocalizedMessages.MessageListenerFilterUnknownListenerHandle);
}
if (package.Callbacks.Count == 0)
{
package.Listener.Dispose();
_packages.Remove(package);
}
}
}
private class RegisteredListener
{
public Dictionary<uint, Action<WindowMessageEventArgs>> Callbacks { get; private set; }
public MessageListener Listener { get; private set; }
public RegisteredListener()
{
Callbacks = new Dictionary<uint, Action<WindowMessageEventArgs>>();
Listener = new MessageListener();
Listener.MessageReceived += MessageReceived;
}
private void MessageReceived(object sender, WindowMessageEventArgs e)
{
if (Callbacks.TryGetValue(e.Message.Msg, out var action))
{
action(e);
}
}
private uint _lastMessage = MessageListener.BaseUserMessage;
public bool TryRegister(Action<WindowMessageEventArgs> callback, out uint message)
{
message = 0;
if (Callbacks.Count < ushort.MaxValue - MessageListener.BaseUserMessage)
{
var i = _lastMessage + 1;
while (i != _lastMessage)
{
if (i > ushort.MaxValue) { i = MessageListener.BaseUserMessage; }
if (!Callbacks.ContainsKey(i))
{
_lastMessage = message = i;
Callbacks.Add(i, callback);
return true;
}
i++;
}
}
return false;
}
}
}
internal class MessageListenerFilterRegistrationResult
{
internal MessageListenerFilterRegistrationResult(nint handle, uint msg)
{
WindowHandle = handle;
Message = msg;
}
public nint WindowHandle { get; private set; }
public uint Message { get; private set; }
}

View File

@@ -0,0 +1,40 @@
using System;
namespace MicaSetup.Shell.Dialogs;
public class ShellObjectNotificationEventArgs : EventArgs
{
public ShellObjectChangeTypes ChangeType { get; private set; }
public bool FromSystemInterrupt { get; private set; }
internal ShellObjectNotificationEventArgs(ChangeNotifyLock notifyLock)
{
ChangeType = notifyLock.ChangeType;
FromSystemInterrupt = notifyLock.FromSystemInterrupt;
}
}
public class ShellObjectChangedEventArgs : ShellObjectNotificationEventArgs
{
public string Path { get; private set; }
internal ShellObjectChangedEventArgs(ChangeNotifyLock notifyLock)
: base(notifyLock) => Path = notifyLock.ItemName;
}
public class ShellObjectRenamedEventArgs : ShellObjectChangedEventArgs
{
public string NewPath { get; private set; }
internal ShellObjectRenamedEventArgs(ChangeNotifyLock notifyLock)
: base(notifyLock) => NewPath = notifyLock.ItemName2;
}
public class SystemImageUpdatedEventArgs : ShellObjectNotificationEventArgs
{
public int ImageIndex { get; private set; }
internal SystemImageUpdatedEventArgs(ChangeNotifyLock notifyLock)
: base(notifyLock) => ImageIndex = notifyLock.ImageIndex;
}

View File

@@ -0,0 +1,427 @@
using System;
using System.ComponentModel;
using System.Threading;
namespace MicaSetup.Shell.Dialogs;
public class ShellObjectWatcher : IDisposable
{
private readonly ShellObject _shellObject;
private readonly bool _recursive;
private readonly ChangeNotifyEventManager _manager = new();
private readonly nint _listenerHandle;
private readonly uint _message;
private uint _registrationId;
private volatile bool _running;
private readonly SynchronizationContext _context = SynchronizationContext.Current;
public ShellObjectWatcher(ShellObject shellObject, bool recursive)
{
if (_context == null)
{
_context = new SynchronizationContext();
SynchronizationContext.SetSynchronizationContext(_context);
}
_shellObject = shellObject ?? throw new ArgumentNullException("shellObject");
_recursive = recursive;
var result = MessageListenerFilter.Register(OnWindowMessageReceived);
_listenerHandle = result.WindowHandle;
_message = result.Message;
}
public bool Running
{
get => _running;
private set => _running = value;
}
public void Start()
{
if (Running) { return; }
var entry = new SHChangeNotifyEntry
{
recursively = _recursive,
pIdl = _shellObject.PIDL
};
_registrationId = Shell32.SHChangeNotifyRegister(
_listenerHandle,
ShellChangeNotifyEventSource.ShellLevel | ShellChangeNotifyEventSource.InterruptLevel | ShellChangeNotifyEventSource.NewDelivery,
_manager.RegisteredTypes,
_message,
1,
ref entry);
if (_registrationId == 0)
{
throw new Win32Exception(LocalizedMessages.ShellObjectWatcherRegisterFailed);
}
Running = true;
}
public void Stop()
{
if (!Running) { return; }
if (_registrationId > 0)
{
Shell32.SHChangeNotifyDeregister(_registrationId);
_registrationId = 0;
}
Running = false;
}
private void OnWindowMessageReceived(WindowMessageEventArgs e)
{
if (e.Message.Msg == _message)
{
_context.Send(x => ProcessChangeNotificationEvent(e), null);
}
}
private void ThrowIfRunning()
{
if (Running)
{
throw new InvalidOperationException(LocalizedMessages.ShellObjectWatcherUnableToChangeEvents);
}
}
protected virtual void ProcessChangeNotificationEvent(WindowMessageEventArgs e)
{
if (!Running) { return; }
if (e == null) { throw new ArgumentNullException("e"); }
var notifyLock = new ChangeNotifyLock(e.Message);
ShellObjectNotificationEventArgs args = notifyLock.ChangeType switch
{
ShellObjectChangeTypes.DirectoryRename or ShellObjectChangeTypes.ItemRename => new ShellObjectRenamedEventArgs(notifyLock),
ShellObjectChangeTypes.SystemImageUpdate => new SystemImageUpdatedEventArgs(notifyLock),
_ => new ShellObjectChangedEventArgs(notifyLock),
};
_manager.Invoke(this, notifyLock.ChangeType, args);
}
public event EventHandler<ShellObjectNotificationEventArgs> AllEvents
{
add
{
ThrowIfRunning();
_manager.Register(ShellObjectChangeTypes.AllEventsMask, value);
}
remove
{
ThrowIfRunning();
_manager.Unregister(ShellObjectChangeTypes.AllEventsMask, value);
}
}
public event EventHandler<ShellObjectNotificationEventArgs> GlobalEvents
{
add
{
ThrowIfRunning();
_manager.Register(ShellObjectChangeTypes.GlobalEventsMask, value);
}
remove
{
ThrowIfRunning();
_manager.Unregister(ShellObjectChangeTypes.GlobalEventsMask, value);
}
}
public event EventHandler<ShellObjectNotificationEventArgs> DiskEvents
{
add
{
ThrowIfRunning();
_manager.Register(ShellObjectChangeTypes.DiskEventsMask, value);
}
remove
{
ThrowIfRunning();
_manager.Unregister(ShellObjectChangeTypes.DiskEventsMask, value);
}
}
public event EventHandler<ShellObjectRenamedEventArgs> ItemRenamed
{
add
{
ThrowIfRunning();
_manager.Register(ShellObjectChangeTypes.ItemRename, value);
}
remove
{
ThrowIfRunning();
_manager.Unregister(ShellObjectChangeTypes.ItemRename, value);
}
}
public event EventHandler<ShellObjectChangedEventArgs> ItemCreated
{
add
{
ThrowIfRunning();
_manager.Register(ShellObjectChangeTypes.ItemCreate, value);
}
remove
{
ThrowIfRunning();
_manager.Unregister(ShellObjectChangeTypes.ItemCreate, value);
}
}
public event EventHandler<ShellObjectChangedEventArgs> ItemDeleted
{
add
{
ThrowIfRunning();
_manager.Register(ShellObjectChangeTypes.ItemDelete, value);
}
remove
{
ThrowIfRunning();
_manager.Unregister(ShellObjectChangeTypes.ItemDelete, value);
}
}
public event EventHandler<ShellObjectChangedEventArgs> Updated
{
add
{
ThrowIfRunning();
_manager.Register(ShellObjectChangeTypes.Update, value);
}
remove
{
ThrowIfRunning();
_manager.Unregister(ShellObjectChangeTypes.Update, value);
}
}
public event EventHandler<ShellObjectChangedEventArgs> DirectoryUpdated
{
add
{
ThrowIfRunning();
_manager.Register(ShellObjectChangeTypes.DirectoryContentsUpdate, value);
}
remove
{
ThrowIfRunning();
_manager.Unregister(ShellObjectChangeTypes.DirectoryContentsUpdate, value);
}
}
public event EventHandler<ShellObjectRenamedEventArgs> DirectoryRenamed
{
add
{
ThrowIfRunning();
_manager.Register(ShellObjectChangeTypes.DirectoryRename, value);
}
remove
{
ThrowIfRunning();
_manager.Unregister(ShellObjectChangeTypes.DirectoryRename, value);
}
}
public event EventHandler<ShellObjectChangedEventArgs> DirectoryCreated
{
add
{
ThrowIfRunning();
_manager.Register(ShellObjectChangeTypes.DirectoryCreate, value);
}
remove
{
ThrowIfRunning();
_manager.Unregister(ShellObjectChangeTypes.DirectoryCreate, value);
}
}
public event EventHandler<ShellObjectChangedEventArgs> DirectoryDeleted
{
add
{
ThrowIfRunning();
_manager.Register(ShellObjectChangeTypes.DirectoryDelete, value);
}
remove
{
ThrowIfRunning();
_manager.Unregister(ShellObjectChangeTypes.DirectoryDelete, value);
}
}
public event EventHandler<ShellObjectChangedEventArgs> MediaInserted
{
add
{
ThrowIfRunning();
_manager.Register(ShellObjectChangeTypes.MediaInsert, value);
}
remove
{
ThrowIfRunning();
_manager.Unregister(ShellObjectChangeTypes.MediaInsert, value);
}
}
public event EventHandler<ShellObjectChangedEventArgs> MediaRemoved
{
add
{
ThrowIfRunning();
_manager.Register(ShellObjectChangeTypes.MediaRemove, value);
}
remove
{
ThrowIfRunning();
_manager.Unregister(ShellObjectChangeTypes.MediaRemove, value);
}
}
public event EventHandler<ShellObjectChangedEventArgs> DriveAdded
{
add
{
ThrowIfRunning();
_manager.Register(ShellObjectChangeTypes.DriveAdd, value);
}
remove
{
ThrowIfRunning();
_manager.Unregister(ShellObjectChangeTypes.DriveAdd, value);
}
}
public event EventHandler<ShellObjectChangedEventArgs> DriveRemoved
{
add
{
ThrowIfRunning();
_manager.Register(ShellObjectChangeTypes.DriveRemove, value);
}
remove
{
ThrowIfRunning();
_manager.Unregister(ShellObjectChangeTypes.DriveRemove, value);
}
}
public event EventHandler<ShellObjectChangedEventArgs> FolderNetworkShared
{
add
{
ThrowIfRunning();
_manager.Register(ShellObjectChangeTypes.NetShare, value);
}
remove
{
ThrowIfRunning();
_manager.Unregister(ShellObjectChangeTypes.NetShare, value);
}
}
public event EventHandler<ShellObjectChangedEventArgs> FolderNetworkUnshared
{
add
{
ThrowIfRunning();
_manager.Register(ShellObjectChangeTypes.NetUnshare, value);
}
remove
{
ThrowIfRunning();
_manager.Unregister(ShellObjectChangeTypes.NetUnshare, value);
}
}
public event EventHandler<ShellObjectChangedEventArgs> ServerDisconnected
{
add
{
ThrowIfRunning();
_manager.Register(ShellObjectChangeTypes.ServerDisconnect, value);
}
remove
{
ThrowIfRunning();
_manager.Unregister(ShellObjectChangeTypes.ServerDisconnect, value);
}
}
public event EventHandler<ShellObjectChangedEventArgs> SystemImageChanged
{
add
{
ThrowIfRunning();
_manager.Register(ShellObjectChangeTypes.SystemImageUpdate, value);
}
remove
{
ThrowIfRunning();
_manager.Unregister(ShellObjectChangeTypes.SystemImageUpdate, value);
}
}
public event EventHandler<ShellObjectChangedEventArgs> FreeSpaceChanged
{
add
{
ThrowIfRunning();
_manager.Register(ShellObjectChangeTypes.FreeSpace, value);
}
remove
{
ThrowIfRunning();
_manager.Unregister(ShellObjectChangeTypes.FreeSpace, value);
}
}
public event EventHandler<ShellObjectChangedEventArgs> FileTypeAssociationChanged
{
add
{
ThrowIfRunning();
_manager.Register(ShellObjectChangeTypes.AssociationChange, value);
}
remove
{
ThrowIfRunning();
_manager.Unregister(ShellObjectChangeTypes.AssociationChange, value);
}
}
protected virtual void Dispose(bool disposing)
{
Stop();
_manager.UnregisterAll();
if (_listenerHandle != 0)
{
MessageListenerFilter.Unregister(_listenerHandle, _message);
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
~ShellObjectWatcher()
{
Dispose(false);
}
}