Skip to content

Fix drag mechanism in Plugin Management Menu - #894

Merged
neon-nyan merged 10 commits into
mainfrom
plugin
Aug 1, 2026
Merged

Fix drag mechanism in Plugin Management Menu#894
neon-nyan merged 10 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
Comment thread CollapseLauncher/XAMLs/MainApp/Pages/PluginManagerPage.xaml.cs Outdated

@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

Comment thread CollapseLauncher/Classes/Helper/WindowUtility.cs Outdated
Comment thread CollapseLauncher/Classes/Helper/WindowUtility.cs Outdated
Comment thread CollapseLauncher/Classes/Helper/WindowUtility.cs Outdated
Comment thread CollapseLauncher/XAMLs/MainApp/Pages/PluginManagerPage.xaml.cs Outdated
Comment thread CollapseLauncher/XAMLs/MainApp/Pages/PluginManagerPage.xaml.cs Outdated
Comment thread CollapseLauncher/XAMLs/MainApp/Pages/PluginManagerPage.xaml.cs Outdated
Comment thread CollapseLauncher/XAMLs/MainApp/Pages/PluginManagerPage.xaml.cs Outdated
Comment thread Hi3Helper.Core/Lang/en_US.json Outdated
Comment on lines +171 to +181
{
_isWinUiFileDragActive = false;
SetImportDropIndicator(false);

if (!IsPointInDropArea(dropPoint))
{
return;
}

await ImportPlugins(selectedFiles);
}

This comment was marked as outdated.

Comment on lines +694 to +701
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));
}

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 FileDropEvent is invoked with a nullable files array, but the OnNativeFileDrop handler and subsequent ImportPlugins call access it without a null check, risking a NullReferenceException.
Severity: HIGH

Suggested Fix

Add a null check for the files variable in WindowUtility.cs before invoking the FileDropEvent. For example: if (files != null) { FileDropEvent?.Invoke(files, ...); }. This ensures the event is only fired with a valid file list.

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/Classes/Helper/WindowUtility.cs#L694-L701

Potential issue: The `NativeFileDrop.TryHandleFileDrop` method can return a `null`
`files` array, as indicated by its `out string[]?` parameter. The `FileDropEvent` is
then invoked with this potentially null array. The event handler, `OnNativeFileDrop`,
expects a non-nullable `string[]` and passes it to the `ImportPlugins` method, which
immediately accesses `selectedFiles.Length` without any null validation. This will cause
a `NullReferenceException` if a file drop operation results in a `true` return from
`TryHandleFileDrop` but with a `null` file list. The exception occurs in an `async void`
handler, making it difficult to handle gracefully.

Also affects:

  • CollapseLauncher/XAMLs/MainApp/Pages/PluginManagerPage.xaml.cs:227~227

Comment on lines +174 to +184
try
{
_isWinUiFileDragActive = false;
SetImportDropIndicator(false);

if (!IsPointInDropArea(dropPoint))
{
return;
}

await ImportPlugins(selectedFiles);

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 OnNativeFileDrop handler for native file drops lacks a guard to prevent concurrent executions, creating a race condition if multiple drops occur quickly.
Severity: MEDIUM

Suggested Fix

In the OnNativeFileDrop method, add a check to ensure an import is not already in progress before calling ImportPlugins. For example: if (!ImportBoxButton.IsEnabled) return;.

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#L172-L184

Potential issue: A race condition can occur if multiple file drop events are processed
in quick succession. The `OnNativeFileDrop` event handler, which is triggered by native
window messages, calls `await ImportPlugins` without checking if an import operation is
already in progress. While other UI-driven import paths check if
`ImportBoxButton.IsEnabled` is `false` to prevent concurrent operations, this native
drop handler lacks that guard. This can lead to multiple `ImportPlugins` tasks running
concurrently, causing unsafe concurrent modifications to the `PluginCollection`, which
is not thread-safe. This could result in collection corruption or duplicate entries.

@neon-nyan
neon-nyan requested a review from bagusnl August 1, 2026 10:41

@neon-nyan neon-nyan 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.

As per changes above. Tested the functionality and should be good for now.

@neon-nyan
neon-nyan merged commit 80947e1 into main Aug 1, 2026
8 checks passed
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.

3 participants