-
Notifications
You must be signed in to change notification settings - Fork 7.5k
feat: Add Media Preview Handler for File Explorer (Issue #44094) #44097
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
feat: Add Media Preview Handler for File Explorer (Issue #44094) #44097
Conversation
src/modules/previewpane/MediaPreviewHandlerCpp/MediaPreviewHandlerCpp.vcxproj
Fixed
Show fixed
Hide fixed
src/modules/previewpane/MediaPreviewHandlerCpp/MediaPreviewHandlerCpp.vcxproj
Fixed
Show fixed
Hide fixed
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR implements a new Media Preview Handler for PowerToys File Explorer, enabling direct preview of audio and video files in the File Explorer preview pane. The implementation follows the established PowerToys preview handler architecture with a C++ COM DLL component that interfaces with File Explorer and a .NET WinForms application using WebView2 to render HTML5 media players.
Key Changes:
- Adds MediaPreviewHandlerCpp (C++ DLL) implementing IPreviewHandler COM interface for File Explorer integration
- Adds MediaPreviewHandler (.NET EXE) providing WebView2-based HTML5 player with dark theme support for audio/video playback
- Registers video formats (.mp4, .avi, .mkv, .mov, .webm, .wmv, .m4v, .3gp, .3g2) and audio formats (.mp3, .wav, .flac, .m4a, .aac, .ogg, .wma) in the Windows registry
Reviewed changes
Copilot reviewed 22 out of 22 changed files in this pull request and generated 12 comments.
Show a summary per file
| File | Description |
|---|---|
| src/modules/previewpane/MediaPreviewHandlerCpp/dllmain.cpp | COM DLL entry point with class factory registration for CLSID {D3A86E9B-5F4C-4A8D-9E76-2B1F8C7E3A4D} |
| src/modules/previewpane/MediaPreviewHandlerCpp/MediaPreviewHandler.cpp | IPreviewHandler implementation launching .NET EXE via ShellExecuteEx |
| src/modules/previewpane/MediaPreviewHandlerCpp/ClassFactory.cpp | COM class factory for MediaPreviewHandler instantiation |
| src/modules/previewpane/MediaPreviewHandler/Program.cs | Entry point parsing command-line args (file path, HWND, rect) and initializing preview control |
| src/modules/previewpane/MediaPreviewHandler/MediaPreviewControl.cs | WebView2 control generating HTML5 video/audio players with dark theme styling |
| src/common/interop/shared_constants.h | Adds MEDIA_PREVIEW_RESIZE_EVENT constant for IPC between C++ and .NET processes |
| src/common/interop/Constants.idl/h/cpp | Exposes MediaPreviewResizeEvent() method to .NET via WinRT projection |
| src/common/logger/logger_settings.h | Adds MediaPrevHandler logger configuration |
| src/common/utils/modulesRegistry.h | Registers media file extensions with preview handler CLSID in Windows registry |
| PowerToys.slnx | Adds MediaPreviewHandler and MediaPreviewHandlerCpp projects to solution |
| <source src=""{fileUrl}"" type=""{mimeType}""> | ||
| Your browser does not support the audio tag. | ||
| </audio> | ||
| <div class=""file-name"">{Path.GetFileName(filePath)}</div> |
Copilot
AI
Dec 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential XSS vulnerability: The file name is inserted directly into the HTML without HTML encoding on line 162. If a malicious file name contains HTML/JavaScript (e.g., <script>alert('xss')</script>.mp3), it could be executed in the WebView2 context. Consider HTML-encoding the file name:
<div class=""file-name"">{System.Web.HttpUtility.HtmlEncode(Path.GetFileName(filePath))}</div>Or use System.Net.WebUtility.HtmlEncode() if System.Web is not available.
| // Disable external navigation | ||
| _webView2Control.CoreWebView2.Settings.IsScriptEnabled = true; | ||
| _webView2Control.CoreWebView2.Settings.AreDefaultContextMenusEnabled = false; | ||
| _webView2Control.CoreWebView2.Settings.AreDevToolsEnabled = false; | ||
|
|
||
| // Generate and navigate to HTML content | ||
| var htmlContent = GenerateMediaHtml(filePath); | ||
| _webView2Control.CoreWebView2.NavigateToString(htmlContent); |
Copilot
AI
Dec 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing navigation protection: While script is enabled for the player controls, there's no handler to prevent navigation away from the media content. If the HTML or media file contains links or redirects, users could potentially navigate to external content. Consider adding a NavigationStarting event handler to block navigation:
_webView2Control.CoreWebView2.NavigationStarting += (s, e) =>
{
if (!e.IsUserInitiated || e.Uri != "about:blank")
{
e.Cancel = true;
}
};| string filePath = args[0]; | ||
| IntPtr hwnd = IntPtr.Parse(args[1], NumberStyles.HexNumber, CultureInfo.InvariantCulture); | ||
|
|
||
| int left = Convert.ToInt32(args[2], 10); | ||
| int right = Convert.ToInt32(args[3], 10); | ||
| int top = Convert.ToInt32(args[4], 10); | ||
| int bottom = Convert.ToInt32(args[5], 10); | ||
| Rectangle s = new Rectangle(left, top, right - left, bottom - top); | ||
|
|
||
| _previewHandlerControl = new MediaPreviewControl(); | ||
|
|
Copilot
AI
Dec 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing error handling for argument parsing. If any of the arguments contain invalid values, IntPtr.Parse() or Convert.ToInt32() will throw exceptions that aren't caught. Consider wrapping the argument parsing in a try-catch block to handle format exceptions gracefully:
try
{
string filePath = args[0];
IntPtr hwnd = IntPtr.Parse(args[1], NumberStyles.HexNumber, CultureInfo.InvariantCulture);
// ... rest of parsing
}
catch (FormatException ex)
{
MessageBox.Show($"Invalid argument format: {ex.Message}");
return;
}| string filePath = args[0]; | |
| IntPtr hwnd = IntPtr.Parse(args[1], NumberStyles.HexNumber, CultureInfo.InvariantCulture); | |
| int left = Convert.ToInt32(args[2], 10); | |
| int right = Convert.ToInt32(args[3], 10); | |
| int top = Convert.ToInt32(args[4], 10); | |
| int bottom = Convert.ToInt32(args[5], 10); | |
| Rectangle s = new Rectangle(left, top, right - left, bottom - top); | |
| _previewHandlerControl = new MediaPreviewControl(); | |
| try | |
| { | |
| string filePath = args[0]; | |
| IntPtr hwnd = IntPtr.Parse(args[1], NumberStyles.HexNumber, CultureInfo.InvariantCulture); | |
| int left = Convert.ToInt32(args[2], 10); | |
| int right = Convert.ToInt32(args[3], 10); | |
| int top = Convert.ToInt32(args[4], 10); | |
| int bottom = Convert.ToInt32(args[5], 10); | |
| Rectangle s = new Rectangle(left, top, right - left, bottom - top); | |
| _previewHandlerControl = new MediaPreviewControl(); | |
| if (!_previewHandlerControl.SetWindow(hwnd, s)) | |
| { | |
| return; | |
| } | |
| _previewHandlerControl.DoPreview(filePath); | |
| NativeEventWaiter.WaitForEventLoop( | |
| Constants.MediaPreviewResizeEvent(), | |
| () => | |
| { | |
| Rectangle s = default; | |
| if (!_previewHandlerControl.SetRect(s)) | |
| { | |
| etwTrace?.Dispose(); | |
| // When the parent HWND became invalid, the application won't respond to Application.Exit(). | |
| Environment.Exit(0); | |
| } | |
| }, | |
| Dispatcher.CurrentDispatcher, | |
| _tokenSource.Token); | |
| etwTrace?.Dispose(); | |
| } | |
| catch (FormatException ex) | |
| { | |
| MessageBox.Show($"Invalid argument format: {ex.Message}"); | |
| return; | |
| } | |
| catch (OverflowException ex) | |
| { | |
| MessageBox.Show($"Argument value out of range: {ex.Message}"); | |
| return; | |
| } | |
| catch (ArgumentNullException ex) | |
| { | |
| MessageBox.Show($"Missing argument: {ex.Message}"); | |
| return; | |
| } |
| { | ||
| if (args.Length == 6) | ||
| { | ||
| ETWTrace etwTrace = new ETWTrace(Path.Combine(Environment.GetEnvironmentVariable("USERPROFILE"), "AppData", "LocalLow", "Microsoft", "PowerToys", "etw")); |
Copilot
AI
Dec 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Path construction issue: Using Environment.GetEnvironmentVariable("USERPROFILE") with string concatenation is error-prone. If the environment variable is not set, this will result in a null reference. Use Path.Combine() with Environment.GetFolderPath() instead:
ETWTrace etwTrace = new ETWTrace(Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
"AppData", "LocalLow", "Microsoft", "PowerToys", "etw"));| private static readonly HashSet<string> VideoExtensions = new(StringComparer.OrdinalIgnoreCase) | ||
| { | ||
| ".mp4", ".3g2", ".3gp", ".3gp2", ".3gpp", ".asf", ".avi", ".m2t", ".m2ts", | ||
| ".m4v", ".mkv", ".mov", ".mp4v", ".mts", ".wm", ".wmv", ".webm", | ||
| }; | ||
|
|
||
| /// <summary> | ||
| /// Supported audio file extensions. | ||
| /// </summary> | ||
| private static readonly HashSet<string> AudioExtensions = new(StringComparer.OrdinalIgnoreCase) | ||
| { | ||
| ".aac", ".ac3", ".amr", ".flac", ".m4a", ".mp3", ".ogg", ".wav", ".wma", | ||
| }; |
Copilot
AI
Dec 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
File extension mismatch: The C# code supports more video/audio extensions (.3gp2, .3gpp, .asf, .m2t, .m2ts, .mp4v, .mts, .wm, .ac3, .amr) than are registered in modulesRegistry.h (lines 23-24). Users will see these files as previewable when they register in File Explorer, but the registry won't have handlers for them. Either add the missing extensions to modulesRegistry.h or remove unsupported extensions from the C# code to ensure consistency.
| ? $@"<video id=""player"" controls autoplay style=""max-width: 100%; max-height: 100%; object-fit: contain;""> | ||
| <source src=""{fileUrl}"" type=""{mimeType}""> | ||
| Your browser does not support the video tag. | ||
| </video>" | ||
| : $@"<div class=""audio-container""> | ||
| <div class=""audio-icon"">🎵</div> | ||
| <audio id=""player"" controls autoplay style=""width: 100%;""> |
Copilot
AI
Dec 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The autoplay attribute is used for both video and audio elements (lines 152, 158). Auto-playing media in a preview pane could be surprising or unwanted behavior for users browsing files. Consider removing autoplay or making it configurable through settings, especially for audio files which might play unexpectedly when users are just browsing through their files in File Explorer.
| ? $@"<video id=""player"" controls autoplay style=""max-width: 100%; max-height: 100%; object-fit: contain;""> | |
| <source src=""{fileUrl}"" type=""{mimeType}""> | |
| Your browser does not support the video tag. | |
| </video>" | |
| : $@"<div class=""audio-container""> | |
| <div class=""audio-icon"">🎵</div> | |
| <audio id=""player"" controls autoplay style=""width: 100%;""> | |
| ? $@"<video id=""player"" controls style=""max-width: 100%; max-height: 100%; object-fit: contain;""> | |
| <source src=""{fileUrl}"" type=""{mimeType}""> | |
| Your browser does not support the video tag. | |
| </video>" | |
| : $@"<div class=""audio-container""> | |
| <div class=""audio-icon"">🎵</div> | |
| <audio id=""player"" controls style=""width: 100%;""> |
| } | ||
|
|
||
| MediaPreviewHandler::~MediaPreviewHandler() | ||
| { |
Copilot
AI
Dec 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The destructor does not clean up the event handle created in the constructor. The m_resizeEvent handle created with CreateEvent() should be closed with CloseHandle(m_resizeEvent) to avoid a handle leak. Consider adding:
if (m_resizeEvent)
{
CloseHandle(m_resizeEvent);
m_resizeEvent = NULL;
}| { | |
| { | |
| if (m_resizeEvent) | |
| { | |
| CloseHandle(m_resizeEvent); | |
| m_resizeEvent = NULL; | |
| } |
| ShellExecuteEx(&sei); | ||
|
|
||
| // Prevent to leak processes: preview is called multiple times when minimizing and restoring Explorer window | ||
| if (m_process) | ||
| { | ||
| TerminateProcess(m_process, 0); | ||
| } | ||
|
|
||
| m_process = sei.hProcess; |
Copilot
AI
Dec 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing error handling for ShellExecuteEx(). If the function fails, sei.hProcess will be NULL, but this isn't checked before assigning to m_process. This could lead to attempting to terminate a NULL handle later. Consider checking the return value:
if (!ShellExecuteEx(&sei))
{
Logger::error(L"Failed to start MediaPreviewHandler.exe. Error code: {}", GetLastError());
return E_FAIL;
}
if (sei.hProcess == NULL)
{
Logger::error(L"ShellExecuteEx succeeded but hProcess is NULL");
return E_FAIL;
}| // Prevent to leak processes: preview is called multiple times when minimizing and restoring Explorer window | ||
| if (m_process) | ||
| { | ||
| TerminateProcess(m_process, 0); |
Copilot
AI
Dec 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Process handle leak: Before calling TerminateProcess(), the old process handle in m_process should be closed with CloseHandle() to avoid leaking handles. Consider:
if (m_process)
{
TerminateProcess(m_process, 0);
CloseHandle(m_process);
}
m_process = sei.hProcess;| TerminateProcess(m_process, 0); | |
| TerminateProcess(m_process, 0); | |
| CloseHandle(m_process); |
|
|
||
| IFACEMETHODIMP MediaPreviewHandler::Unload() | ||
| { | ||
| TerminateProcess(m_process, 0); |
Copilot
AI
Dec 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing process handle cleanup and null check. Before calling TerminateProcess(), check if m_process is not NULL, and after termination, close the handle:
if (m_process)
{
TerminateProcess(m_process, 0);
CloseHandle(m_process);
m_process = NULL;
}| TerminateProcess(m_process, 0); | |
| if (m_process) | |
| { | |
| TerminateProcess(m_process, 0); | |
| CloseHandle(m_process); | |
| m_process = NULL; | |
| } |
) - Add MediaPreviewHandlerCpp DLL (COM IPreviewHandler) - Add MediaPreviewHandler .NET EXE (WebView2 HTML5 player) - Support video: .mp4, .avi, .mkv, .mov, .webm, .wmv, .m4v, .3gp - Support audio: .mp3, .wav, .flac, .m4a, .aac, .ogg, .wma - Add MediaPreviewResizeEvent constant - Add file extension registration in modulesRegistry.h
This comment has been minimized.
This comment has been minimized.
6f9f49e to
eee1a6a
Compare
Summary
Implements media playback preview in File Explorer's preview panel, addressing #44094.
This enables users to preview audio and video files directly in File Explorer's preview pane with full playback controls.
Changes
New Projects
Supported Formats
.mp4,.avi,.mkv,.mov,.webm,.wmv,.m4v,.3gp,.3g2.mp3,.wav,.flac,.m4a,.aac,.ogg,.wmaModified Files
shared_constants.h- AddedMEDIA_PREVIEW_RESIZE_EVENTConstants.h/cpp/idl- AddedMediaPreviewResizeEvent()logger_settings.h- Added media preview loggermodulesRegistry.h- Added file extension registrationPowerToys.slnx- Added projects to solutionTODO (Can be done in follow-up PRs)
1. Settings UI Toggle
Add enable/disable toggle in PowerToys Settings for Media Preview Handler.
Files to modify:
src/modules/previewpane/powerpreview/powerpreview.cpp- Add settings handlersrc/settings-ui/- Add UI toggle similar to other preview handlers2. Installer Integration
Add files to WiX installer manifest.
Files to modify:
installer/PowerToysSetup/Resources.wxs- Add MediaPreviewHandler files3. Additional Format Support
Consider adding more formats:
.flv,.ts,.m2ts,.vob.aiff,.opus,.ape4. Codec Handling
Add detection for missing codecs (e.g., HEVC/H.265) similar to Peek's
GetMissingCodecAsync()inVideoPreviewer.cs.5. Thumbnail Generation
Optionally add a
MediaThumbnailProviderfor generating video thumbnails (similar toSvgThumbnailProvider).Checklist
Fixes #44094