Skip to content

Fix drag mechanism in Plugin Management Menu#894

Open
Cryotechnic wants to merge 6 commits into
mainfrom
plugin
Open

Fix drag mechanism in Plugin Management Menu#894
Cryotechnic wants to merge 6 commits into
mainfrom
plugin

Conversation

@Cryotechnic

Copy link
Copy Markdown
Member

Main Goal

Add drag-and-drop plugin importing to PluginManagerPage, including support for dragging files from unelevated Windows Explorer into Collapse’s elevated process, while retaining the existing file-picker workflow.

PR Status :

  • Overall Status : Done
  • Commits : Done
  • Synced to base (Collapse:main) : Yes
  • Build status : OK
  • Crashing : No
  • Bug found caused by PR : 0

Changelog

[New] Added drag-and-drop plugin importing to PluginManagerPage.

  • Enabled dropping on ImportBoxButton through AllowDrop="True" and the following XAML handlers:
    • OnDragEnterImportBox
    • OnDragOverImportBox
    • OnDragLeaveImportBox
    • OnDropImportBox
  • OnDropImportBox retrieves StorageFile objects from DragEventArgs.DataView.GetStorageItemsAsync() and converts them into file-system paths.
  • Supports dropping multiple plugin files in one operation.
  • Dropped .zip packages and manifest.json files are passed to the same ImportPlugins method used by the existing file picker.
  • ImportPlugins calls PluginImporter.AutoGetImportFromPath for each path and adds successfully imported PluginInfo objects to PluginManagerPage.Context.PluginCollection.
  • Individual import failures are collected and reported together through AggregateException and ErrorSender.SendException.
  • The import panel remains disabled while ImportPlugins is running, preventing overlapping imports.

[New] Added elevated-process file-drop support to WindowUtility.

  • Collapse uses requireAdministrator, which prevents normal Explorer drag events from reaching the WinUI drop target because Explorer usually runs at a lower integrity level.
  • Added WindowUtility.SetFileDropEnabled to register the main window with the native DragAcceptFiles API.
  • SetFileDropEnabled uses ChangeWindowMessageFilterEx to permit the native messages required for cross-integrity shell drops:
    • WM_DROPFILES
    • WM_COPYDATA
    • WM_COPYGLOBALDATA
  • Extended WindowUtility.MainWndProc to process WM_DROPFILES and forward its HDROP handle to HandleFileDrop.
  • HandleFileDrop uses:
    • DragQueryFile to retrieve every dropped path.
    • DragQueryPoint to retrieve the drop coordinates.
    • DragFinish to release the native HDROP handle.
  • Added WindowUtility.FileDropEvent, which passes the collected paths and drop coordinates to PluginManagerPage.OnNativeFileDrop.
  • PluginManagerPage.OnPluginManagerPageLoaded enables native file drops and subscribes to FileDropEvent.
  • OnPluginManagerPageUnloaded disables native file drops and removes the event subscription.
  • OnNativeFileDrop calls IsPointInDropArea so native drops are accepted only when they occur inside ImportBoxButton.

[Imp] Refactored the existing plugin import workflow.

  • OnClickImportButton remains responsible for opening FileDialogNative.GetMultiFilePicker.
  • The picker’s import loop was extracted into ImportPlugins.
  • Both OnClickImportButton and OnDropImportBox now call ImportPlugins, keeping validation, collection updates, partial-success handling, and error reporting consistent between both input methods.
  • Existing picker filters remain limited to .zip and manifest.json.

[Imp] Added drag-hover feedback for both WinUI and elevated Explorer drops.

  • Added the ImportDropIndicator overlay directly above the plugin import panel.

  • The overlay contains:

    • A rounded Rectangle with StrokeDashArray="1,2" and StrokeDashCap="Round".
    • A three-pixel dotted outline using the palette-driven AccentColor theme resource.
    • A subtle background fill using AccentFillColorDefaultBrush.
  • Added a ScalarTransition with a duration of 180 ms to fade the indicator in and out.

  • SetImportDropIndicator tracks the current visibility state and changes the overlay opacity only when necessary.

  • Standard WinUI drag events update _isWinUiFileDragActive and call SetImportDropIndicator.

  • Native WM_DROPFILES does not provide drag-enter or drag-leave notifications, so PluginManagerPage uses _fileDragIndicatorTimer to detect elevated Explorer hover state.

  • OnFileDragIndicatorTimerTick calls WindowUtility.TryGetExternalDragPosition every 50 ms.

  • TryGetExternalDragPosition uses:

    • GetAsyncKeyState to detect left- or right-button dragging.
    • GetCursorPos to obtain the global pointer position.
    • ScreenToClient to convert it into launcher client coordinates.
    • GetForegroundWindow to avoid treating normal clicks inside Collapse as external drags.
  • IsPointInDropArea compares the native pointer coordinates against the transformed bounds of ImportBoxButton, including the current monitor scale factor.

  • [Loc] Updated the English plugin-import instructions.

Templates

Changelog Prefixes
  **[New]**
  **[Imp]**
  **[Fix]**
  **[Loc]**
  **[Doc]**

Convert `WindowUtility` to partial and add file drag-and-drop handling.

- Added `internal FileDropEvent` and `SetFileDropEnabled` to toggle `DROPFILES`/`COPYDATA`/`COPYGLOBALDATA` filters and call `DragAcceptFiles`.
- Implemented `TryGetExternalDragPosition` to read external drag cursor position.
- `WndProc` now handles `WM_DROPFILES` and forwards to `HandleFileDrop`.
- HandleFileDrop enumerates dropped files (`DragQueryFile`/`DragQueryPoint`) and invokes `FileDropEvent`, then cleans up with `DragFinish`.
- Added `NativeFileDrop` partial class with `P/Invoke` bindings and a `NativePoint` struct.
- Enable WinUI and native file drag-and-drop support for the Plugin Manager
- Add drag enter/over/leave/drop handlers
- Add a `DispatcherTimer` to track external drag position, and a visual drop indicator.
- Wire `WindowUtility.FileDropEvent` on page load/unload and gate drops to the import area.
- Extract import flow into an async `ImportPlugins` method and improve error handling around imports.
Comment on lines +167 to +175
private async void OnNativeFileDrop(string[] selectedFiles, PointInt32 dropPoint)
{
if (!IsPointInDropArea(dropPoint))
{
return;
}

await ImportPlugins(selectedFiles);
}

This comment was marked as outdated.

- PluginImporter: support importing `.zip` packages or `manifest.json`, validate filenames, copy assets to a staging directory and atomically move into place, protect against directory-traversal by resolving contained paths (`GetContainedPath`), and ensure cleanup on failure. Uses stream-based copy for assets.
- PluginManagerPage: fixes drag indicator state, collects per-file failures instead of throwing `AggregateException`, logs errors, and shows a friendly dialog mapping common exceptions to readable messages.
@Cryotechnic Cryotechnic changed the title Plugin Fix drag mechanism in Plugin Management Menu Jul 16, 2026
{
return;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: The async void method OnNativeFileDrop lacks a try-catch block when calling await ImportPlugins, which can lead to an unhandled exception and crash the application.
Severity: HIGH

Suggested Fix

Wrap the await ImportPlugins(selectedFiles) call inside the OnNativeFileDrop method with a try-catch block, similar to the implementation in OnClickImportButton and OnDropImportBox. This will catch exceptions and prevent the application from crashing.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location: CollapseLauncher/XAMLs/MainApp/Pages/PluginManagerPage.xaml.cs#L179

Potential issue: The `OnNativeFileDrop` event handler is an `async void` method that
calls `await ImportPlugins(selectedFiles)` without any exception handling. The
`ImportPlugins` method can throw an exception, for example, when its call to
`SimpleDialogs.SpawnDialog` fails. Unlike other event handlers in the same file,
`OnClickImportButton` and `OnDropImportBox`, which wrap the same call in a `try-catch`
block, `OnNativeFileDrop` lacks this protection. An unhandled exception in an `async
void` method will propagate to the `SynchronizationContext` and crash the application.
This can be triggered by a native file drop if the dialog fails to spawn due to UI state
issues or other errors.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need fix just in case UI thread being poo poo and the first two setter drops itself

@bagusnl bagusnl left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some changes are needed especially on the async void error handling and some localizations to user facing errors


NativeFileDrop.DragQueryPoint(dropHandle, out NativePoint dropPoint);
FileDropEvent?.Invoke(files, new PointInt32(dropPoint.X, dropPoint.Y));
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might want to catch any error during the file drop just in case so that

  1. User knows (error message or dialog, telling them to select a file instead using the old mechanism)
  2. We knows (Sentry capture)


private static unsafe partial class NativeFileDrop
{
[LibraryImport("shell32.dll")]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will be much appreciated if all of the static imports is moved towards the submodule Hi3Helper.Win32/Native so we have less unsafe method in the main project

[StructLayout(LayoutKind.Sequential)]
private struct NativePoint
{
internal int X;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By MSFT documentation, the type for tagPOINT is long, is there a specific reason why we use int instead?
https://learn.microsoft.com/en-us/windows/win32/api/windef/ns-windef-point

}
catch (Exception ex)
{
ErrorSender.SendException(ex.WrapPluginException("No plugin has been imported due to following error:"));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider localizing error message as it's sent to the user facing dialog

}
catch (Exception ex)
{
ErrorSender.SendException(ex.WrapPluginException("No plugin has been imported due to following error:"));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above under ErrorSender

{
return;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need fix just in case UI thread being poo poo and the first two setter drops itself

{
NotSupportedException => exception.Message,
DuplicateNameException => exception.Message,
UnauthorizedAccessException => "Permission was denied while reading or installing the plugin.",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Localize

"RightPanelImportTitle2": "or",
"RightPanelImportTitle3": "here",
"RightPanelImportTitle4": "Anywhere in the box is fine!"
"RightPanelImportTitle1": "Browse or drop a",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Click to browse or drop a" seems better message if the UI fits

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants