diff --git a/CollapseLauncher/Classes/Helper/WindowUtility.cs b/CollapseLauncher/Classes/Helper/WindowUtility.cs index 9e98f6314..4a09ee582 100644 --- a/CollapseLauncher/Classes/Helper/WindowUtility.cs +++ b/CollapseLauncher/Classes/Helper/WindowUtility.cs @@ -56,6 +56,7 @@ internal enum WindowBackdropKind internal static class WindowUtility { private static event EventHandler? DragAreaChangeEvent; + internal static event Action? FileDropEvent; private static nint _oldMainWndProcPtr; private static nint _oldDesktopSiteBridgeWndProcPtr; @@ -417,6 +418,52 @@ internal static void RegisterWindow(this Window window) FileDialogNative.InitHandlerPointer(CurrentWindowPtr); } + internal static void SetFileDropEnabled(bool isEnabled) + { + const uint WM_COPYDATA = 0x004A; + const uint WM_COPYGLOBALDATA = 0x0049; + const uint WM_DROPFILES = 0x0233; + const uint MSGFLT_RESET = 0; + const uint MSGFLT_ALLOW = 1; + + nint windowHandle = CurrentWindowPtr; + if (windowHandle == nint.Zero) + { + return; + } + + uint messageFilterAction = isEnabled ? MSGFLT_ALLOW : MSGFLT_RESET; + PInvoke.ChangeWindowMessageFilterEx(windowHandle, WM_DROPFILES, messageFilterAction, nint.Zero); + PInvoke.ChangeWindowMessageFilterEx(windowHandle, WM_COPYDATA, messageFilterAction, nint.Zero); + PInvoke.ChangeWindowMessageFilterEx(windowHandle, WM_COPYGLOBALDATA, messageFilterAction, nint.Zero); + PInvoke.DragAcceptFiles(windowHandle, isEnabled); + } + + internal static bool TryGetExternalDragPosition(out PointInt32 dragPosition) + { + const int VK_LBUTTON = 0x01; + const int VK_RBUTTON = 0x02; + const int KeyPressed = 0x8000; + + dragPosition = default; + nint windowHandle = CurrentWindowPtr; + if (windowHandle == nint.Zero) + { + return false; + } + + bool isMouseButtonPressed = (PInvoke.GetAsyncKeyState(VK_LBUTTON) & KeyPressed) != 0 || + (PInvoke.GetAsyncKeyState(VK_RBUTTON) & KeyPressed) != 0; + if (!isMouseButtonPressed || !PInvoke.GetCursorPos(out POINTL cursorPosition)) + { + return false; + } + + PInvoke.ScreenToClient(windowHandle, ref cursorPosition); + dragPosition = new PointInt32(cursorPosition.x, cursorPosition.y); + return true; + } + #region Drag Area Handler private static void InstallDragAreaChangeMonitor() @@ -496,6 +543,7 @@ private static nint MainWndProc(nint hwnd, uint msg, nuint wParam, nint lParam) const uint WM_ACTIVATE = 0x0006; const uint WM_QUERYENDSESSION = 0x0011; const uint WM_ENDSESSION = 0x0016; + const uint WM_DROPFILES = 0x0233; switch (msg) { @@ -643,6 +691,21 @@ private static nint MainWndProc(nint hwnd, uint msg, nuint wParam, nint lParam) (CurrentWindow as MainWindow)?.CloseApp(); } break; + case WM_DROPFILES: + if (NativeFileDrop.TryHandleFileDrop((nint)wParam, + out string[]? files, + out POINTL dropPoint, + out Exception? ex)) + { + FileDropEvent?.Invoke(files, new PointInt32(dropPoint.x, dropPoint.y)); + } + + if (ex != null) + { + ErrorSender.SendException(ex); + SentryHelper.ExceptionHandler(ex); + } + return 0; } return PInvoke.CallWindowProc(_oldMainWndProcPtr, hwnd, msg, wParam, lParam); diff --git a/CollapseLauncher/Classes/Plugins/PluginImporter.cs b/CollapseLauncher/Classes/Plugins/PluginImporter.cs index b8f3b496b..50b908ca8 100644 --- a/CollapseLauncher/Classes/Plugins/PluginImporter.cs +++ b/CollapseLauncher/Classes/Plugins/PluginImporter.cs @@ -1,5 +1,6 @@ using Hi3Helper.Plugin.Core.Update; using Hi3Helper.Shared.Region; +using CollapseLauncher.Helper.StreamUtility; using System; using System.Data; using System.IO; @@ -15,6 +16,14 @@ internal static partial class PluginImporter public static async Task AutoGetImportFromPath(string filePath, CancellationToken token) { bool isFromPackage = filePath.EndsWith(".zip", StringComparison.OrdinalIgnoreCase); + bool isFromManifest = string.Equals(Path.GetFileName(filePath), + PluginManager.ManifestPrefix, + StringComparison.OrdinalIgnoreCase); + + if (!isFromPackage && !isFromManifest) + { + throw new NotSupportedException($"{Path.GetFileName(filePath)} is not supported. Import a .zip package or a file named manifest.json."); + } using IPluginSource pluginSource = isFromPackage ? await ZipPluginSource.GetSourceFrom(filePath, token) : @@ -24,6 +33,7 @@ await ZipPluginSource.GetSourceFrom(filePath, token) : string pluginDirName = $"Hi3Helper.Plugin.{pluginExecNameNoExt}"; string pluginBaseDir = Path.Combine(LauncherConfig.AppPluginFolder, pluginDirName); string pluginRelName = Path.Combine(pluginDirName, pluginSource.Manifest.MainLibraryName); + string pluginEntryPath = GetContainedPath(pluginBaseDir, pluginSource.Manifest.MainLibraryName); if (PluginManager.PluginInstances.ContainsKey(pluginBaseDir) || PluginManager.PluginInstances.Values @@ -44,25 +54,64 @@ await ZipPluginSource.GetSourceFrom(filePath, token) : }); } - string pluginEntryPath = Path.Combine(pluginBaseDir, pluginSource.Manifest.MainLibraryName); - foreach (PluginManifestAssetInfo asset in pluginSource.Manifest.Assets) + Directory.CreateDirectory(LauncherConfig.AppPluginFolder); + string stagingDir = Path.Combine(LauncherConfig.AppPluginFolder, $".import-{Guid.NewGuid():N}"); + string cleanupDir = stagingDir; + + try { - string assetFullPath = Path.Combine(pluginBaseDir, asset.FilePath); - string? assetFullDir = Path.GetDirectoryName(assetFullPath); + foreach (PluginManifestAssetInfo asset in pluginSource.Manifest.Assets) + { + string assetFullPath = GetContainedPath(stagingDir, asset.FilePath); + string? assetFullDir = Path.GetDirectoryName(assetFullPath); + + if (!string.IsNullOrEmpty(assetFullDir)) + { + Directory.CreateDirectory(assetFullDir); + } + + await using FileStream assetStream = File.Create(assetFullPath); + await using Stream assetPluginSourceStream = await pluginSource.GetAssetStream(asset, token); + + await assetPluginSourceStream.CopyToAsync(assetStream, token); + } + + Directory.Move(stagingDir, pluginBaseDir); + cleanupDir = pluginBaseDir; - if (!string.IsNullOrEmpty(assetFullDir)) + PluginInfo pluginInfo = new(pluginEntryPath, + pluginRelName, + pluginSource.Manifest); + cleanupDir = string.Empty; + return pluginInfo; + } + catch + { + if (!string.IsNullOrEmpty(cleanupDir)) { - Directory.CreateDirectory(assetFullDir); + new DirectoryInfo(cleanupDir).TryDeleteDirectory(true); } - await using FileStream assetStream = File.Create(assetFullPath); - await using Stream assetPluginSourceStream = await pluginSource.GetAssetStream(asset, token); + throw; + } + } + + private static string GetContainedPath(string baseDirectory, string relativePath) + { + if (string.IsNullOrWhiteSpace(relativePath)) + { + throw new InvalidDataException("The plugin manifest contains an empty file path."); + } - await assetPluginSourceStream.CopyToAsync(assetStream, token); + string baseFullPath = Path.TrimEndingDirectorySeparator(Path.GetFullPath(baseDirectory)); + string fileFullPath = Path.GetFullPath(relativePath, baseFullPath); + string basePrefix = baseFullPath + Path.DirectorySeparatorChar; + + if (!fileFullPath.StartsWith(basePrefix, StringComparison.OrdinalIgnoreCase)) + { + throw new InvalidDataException($"The plugin manifest path '{relativePath}' points outside the plugin directory."); } - return new PluginInfo(pluginEntryPath, - pluginRelName, - pluginSource.Manifest); + return fileFullPath; } } diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.Background.cs b/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.Background.cs index 126c7a41c..e238a62fd 100644 --- a/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.Background.cs +++ b/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.Background.cs @@ -148,8 +148,8 @@ private bool IsInMultiBackgroundPipsPagerGridHoverArea(PointerRoutedEventArgs? a } else { - PInvoke.GetCursorPos(out pointerPos).ThrowOnFailure(); - PInvoke.ScreenToClient(WindowUtility.CurrentWindowPtr, ref pointerPos).ThrowOnFailure(); + PInvoke.GetCursorPos(out pointerPos); + PInvoke.ScreenToClient(WindowUtility.CurrentWindowPtr, ref pointerPos); } double xFrom = gridPos.X; diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/PluginManagerPage.xaml b/CollapseLauncher/XAMLs/MainApp/Pages/PluginManagerPage.xaml index 249ce132e..d3e2c4773 100644 --- a/CollapseLauncher/XAMLs/MainApp/Pages/PluginManagerPage.xaml +++ b/CollapseLauncher/XAMLs/MainApp/Pages/PluginManagerPage.xaml @@ -823,12 +823,17 @@ + + + + + + +