2 Commits
Author SHA1 Message Date
FoxyzandClaude Opus 5.5 ef96868b87 0.1.9: Windows build fix (D8021: '/W3 /WX' as one cl argument)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 00:31:25 +03:00
FoxyzandClaude Opus 5.5 d2435a06b2 0.1.8: media_kit hot-restart guard (Linux, debug)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 18:57:23 +03:00
8 changed files with 347 additions and 233 deletions

No files matched your search

+57 -40
View File
@@ -1,41 +1,58 @@
## 0.1.7 ## 0.1.9
* Add `DesktopDropX` + `DropTarget` — OS-level drag & drop of files and * Windows: the plugin builds again. Its per-source compile options passed
images into the app, powered by a vendored desktop_drop engine (v0.7.1, "/W3 /WX" to cl.exe as one argument (error D8021); now plain /W3 for all
Apache-2.0): GTK uri-list drag destination on Linux, OLE `IDropTarget` sources, without warnings-as-errors.
(`CF_HDROP`) on Windows, dead code on mobile and web. `DesktopDropX`
provides `pathsOf` (dropped file paths) and `readImage` (magic-byte ## 0.1.8
image sniffing, same table as `ClipboardX.pasteImage`).
* The Linux/Windows plugin entry is now a combined `FoxUtilsPlugin` * Linux, debug builds only: a Flutter hot restart no longer crashes apps that
registration that wires the vendored pasteboard and desktop_drop engines use media_kit with live players. Hot restart frees the old isolate's
from one native entry per platform (the registered plugin class changed NativeCallables while the mpv handles live on; media_kit then sends the
from `PasteboardPlugin` to `FoxUtilsPlugin` — generated registrants leftovers "quit" and mpv calls a dead wakeup callback (SIGSEGV on mpv's
regenerate on the next build). "core" thread). The plugin clears every live handle's wakeup callback on
* New dependencies: `cross_file` (drop items are `XFile`s) and the engine's `on-pre-engine-restart` signal (handles found through
`universal_platform` (web-safe platform checks), both required by the media_kit's NativeReferenceHolder file). Automatic: depending on fox_utils
vendored engine. is enough. Relies on media_kit internals (checked against 1.2.6).
## 0.1.6 ## 0.1.7
* Add `ClipboardX.pasteImage` — reads an image from the clipboard whether it * Add `DesktopDropX` + `DropTarget` — OS-level drag & drop of files and
was copied as image data (browser) or as a file reference (file manager), images into the app, powered by a vendored desktop_drop engine (v0.7.1,
on Windows, Linux and Android. Adds a `readFileBytes` channel method for Apache-2.0): GTK uri-list drag destination on Linux, OLE `IDropTarget`
resolving Android content URIs. (`CF_HDROP`) on Windows, dead code on mobile and web. `DesktopDropX`
provides `pathsOf` (dropped file paths) and `readImage` (magic-byte
## 0.1.5 image sniffing, same table as `ClipboardX.pasteImage`).
* The Linux/Windows plugin entry is now a combined `FoxUtilsPlugin`
* Add `ClipboardX` — clipboard image/files/text copy via a vendored registration that wires the vendored pasteboard and desktop_drop engines
pasteboard engine (MixinNetwork, Apache-2.0) with modernized builds: from one native entry per platform (the registered plugin class changed
the Android channel is Java (no KGP), the FileProvider is merged from from `PasteboardPlugin` to `FoxUtilsPlugin` — generated registrants
the plugin manifest (zero per-project setup), image writes are byte-faithful regenerate on the next build).
with magic-byte MIME sniffing, and two upstream bugs are fixed. * New dependencies: `cross_file` (drop items are `XFile`s) and
`universal_platform` (web-safe platform checks), both required by the
## 0.1.4 vendored engine.
* Add `AndroidFiles.save` — permission-free file saving to public Android ## 0.1.6
directories (Pictures/Movies/Music/Download) with an optional media scan.
Android platform channel, no storage permissions required. * Add `ClipboardX.pasteImage` — reads an image from the clipboard whether it
was copied as image data (browser) or as a file reference (file manager),
## 0.0.1 on Windows, Linux and Android. Adds a `readFileBytes` channel method for
resolving Android content URIs.
## 0.1.5
* Add `ClipboardX` — clipboard image/files/text copy via a vendored
pasteboard engine (MixinNetwork, Apache-2.0) with modernized builds:
the Android channel is Java (no KGP), the FileProvider is merged from
the plugin manifest (zero per-project setup), image writes are byte-faithful
with magic-byte MIME sniffing, and two upstream bugs are fixed.
## 0.1.4
* Add `AndroidFiles.save` — permission-free file saving to public Android
directories (Pictures/Movies/Music/Download) with an optional media scan.
Android platform channel, no storage permissions required.
## 0.0.1
* TODO: Describe initial release. * TODO: Describe initial release.
+193 -183
View File
@@ -1,183 +1,193 @@
# fox_utils # fox_utils
Reusable Flutter utilities shared across projects: permission-free Android Reusable Flutter utilities shared across projects: permission-free Android
file saving, a cross-platform clipboard wrapper, and desktop drag & drop. file saving, a cross-platform clipboard wrapper, and desktop drag & drop.
## Features ## Features
### `AndroidFiles.save` — save files to public Android storage ### `AndroidFiles.save` — save files to public Android storage
Writes any bytes to a public Android directory (`Pictures`, `Movies`, Writes any bytes to a public Android directory (`Pictures`, `Movies`,
`Music`, `Download`) **without any storage permission**, with an optional `Music`, `Download`) **without any storage permission**, with an optional
media scan so the file shows up in gallery apps. media scan so the file shows up in gallery apps.
```dart ```dart
final path = await AndroidFiles.save( final path = await AndroidFiles.save(
bytes, // any file type bytes, // any file type
fileName: 'image.jpg', fileName: 'image.jpg',
location: AndroidPublicLocation.pictures, // pictures | movies | music | downloads location: AndroidPublicLocation.pictures, // pictures | movies | music | downloads
subPath: 'Imagen', // folder structure under the location subPath: 'Imagen', // folder structure under the location
scan: true, // false = saved but not indexed (no gallery) scan: true, // false = saved but not indexed (no gallery)
); );
``` ```
Notes: Notes:
- Requires **no permissions** on Android 10+ (API 29+): creating new files - Requires **no permissions** on Android 10+ (API 29+): creating new files
inside media collections is permission-free. Legacy devices (< API 29) may inside media collections is permission-free. Legacy devices (< API 29) may
fail — the exception carries the native message. fail — the exception carries the native message.
- `scan: false` writes the file without indexing it. On modern Android the - `scan: false` writes the file without indexing it. On modern Android the
file can still be picked up by a later system rescan; for a hard file can still be picked up by a later system rescan; for a hard
"never in gallery" guarantee, save outside a media collection or add a "never in gallery" guarantee, save outside a media collection or add a
`.nomedia` file to the folder. `.nomedia` file to the folder.
- Throws `FileSaveException` on failure (logged with stack trace). - Throws `FileSaveException` on failure (logged with stack trace).
### `ClipboardX` — clipboard copy ### `ClipboardX` — clipboard copy
```dart ```dart
await ClipboardX.copyImage(bytes); // image bytes as-is, MIME sniffed await ClipboardX.copyImage(bytes); // image bytes as-is, MIME sniffed
await ClipboardX.copyFiles(['/path/a.png']); // desktop: files await ClipboardX.copyFiles(['/path/a.png']); // desktop: files
ClipboardX.copyText('hello'); // text (Flutter built-in) ClipboardX.copyText('hello'); // text (Flutter built-in)
final text = await ClipboardX.readText(); final text = await ClipboardX.readText();
final image = await ClipboardX.readImage(); final image = await ClipboardX.readImage();
final files = await ClipboardX.readFiles(); final files = await ClipboardX.readFiles();
``` ```
Platform matrix: Platform matrix:
| Operation | Android | Windows | Linux | macOS | iOS | | Operation | Android | Windows | Linux | macOS | iOS |
|---|---|---|---|---|---| |---|---|---|---|---|---|
| copy image | ✓ (FileProvider) | ✓ (GDI+) | ✓ (file reference) | ✓ | ✓ | | copy image | ✓ (FileProvider) | ✓ (GDI+) | ✓ (file reference) | ✓ | ✓ |
| copy files | – | ✓ (CF_HDROP) | ✓ (GNOME) | ✓ | – | | copy files | – | ✓ (CF_HDROP) | ✓ (GNOME) | ✓ | – |
| copy/read text | ✓ | ✓ | ✓ | ✓ | ✓ | | copy/read text | ✓ | ✓ | ✓ | ✓ | ✓ |
| read image | ✓ | ✓ | ✓ | ✓ | ✓ | | read image | ✓ | ✓ | ✓ | ✓ | ✓ |
| read files | ✓ (URIs) | ✓ | ✓ | ✓ | – | | read files | ✓ (URIs) | ✓ | ✓ | ✓ | – |
Notes: Notes:
- Image bytes are placed **as-is** (no re-encode); the extension is sniffed - Image bytes are placed **as-is** (no re-encode); the extension is sniffed
from magic bytes (PNG/JPEG/GIF/WebP/BMP), so animated GIFs survive. from magic bytes (PNG/JPEG/GIF/WebP/BMP), so animated GIFs survive.
- **Linux cannot hold raw image bytes** on the clipboard (GTK limitation), so - **Linux cannot hold raw image bytes** on the clipboard (GTK limitation), so
`copyImage` stages the bytes to a rotating temp file and copies it as a `copyImage` stages the bytes to a rotating temp file and copies it as a
file reference — pasted apps receive the image file. The staging file is file reference — pasted apps receive the image file. The staging file is
replaced on each copy. replaced on each copy.
- **Android requires no manifest setup**: the FileProvider is declared in - **Android requires no manifest setup**: the FileProvider is declared in
this plugin's manifest and merged automatically (`${applicationId}.provider`). this plugin's manifest and merged automatically (`${applicationId}.provider`).
Images are staged in the app cache dir. Images are staged in the app cache dir.
- Throws `ClipboardCopyException` on failure. - Throws `ClipboardCopyException` on failure.
### `ClipboardX.pasteImage` — paste an image, whatever the source ### `ClipboardX.pasteImage` — paste an image, whatever the source
```dart ```dart
final Uint8List? image = await ClipboardX.pasteImage(); final Uint8List? image = await ClipboardX.pasteImage();
if (image != null) { if (image != null) {
// send to server, display, whatever // send to server, display, whatever
} }
``` ```
Resolution order: image data on the clipboard first (browser "copy image"), Resolution order: image data on the clipboard first (browser "copy image"),
then the first file reference (file manager "copy"). The file's first bytes then the first file reference (file manager "copy"). The file's first bytes
are sniffed (PNG/JPEG/GIF/WebP/BMP) before the full read, so huge non-image are sniffed (PNG/JPEG/GIF/WebP/BMP) before the full read, so huge non-image
files are never loaded. Returns `null` when the clipboard holds no image. files are never loaded. Returns `null` when the clipboard holds no image.
Source matrix: Source matrix:
| Copied from | Windows | Linux | Android | | Copied from | Windows | Linux | Android |
|---|---|---|---| |---|---|---|---|
| Browser, "copy image" | CF_DIB → image bytes | `image/png` → image bytes | content URI (image mime) → image bytes | | Browser, "copy image" | CF_DIB → image bytes | `image/png` → image bytes | content URI (image mime) → image bytes |
| File manager, "copy" | CF_HDROP → first file's bytes | `text/uri-list` → first file's bytes | content URI → resolved bytes | | File manager, "copy" | CF_HDROP → first file's bytes | `text/uri-list` → first file's bytes | content URI → resolved bytes |
Android notes: Android notes:
- Reading the clipboard works **only while the app is in the foreground** - Reading the clipboard works **only while the app is in the foreground**
(Android 10+); background reads return `null`. (Android 10+); background reads return `null`.
- Android 12+ shows a system toast ("app pasted from your clipboard") on - Android 12+ shows a system toast ("app pasted from your clipboard") on
read — cosmetic. read — cosmetic.
- File references come back as `content://` URIs; `pasteImage` resolves them - File references come back as `content://` URIs; `pasteImage` resolves them
through the platform channel (`readFileBytes`), so no manifest setup or through the platform channel (`readFileBytes`), so no manifest setup or
permission is needed. permission is needed.
### `DesktopDropX` + `DropTarget` — drag & drop files and images ### `DesktopDropX` + `DropTarget` — drag & drop files and images
OS-level drag & drop into the app (desktop only). Wrap any widget in OS-level drag & drop into the app (desktop only). Wrap any widget in
`DropTarget` to get drag enter/exit/update/drop callbacks; `DesktopDropX` `DropTarget` to get drag enter/exit/update/drop callbacks; `DesktopDropX`
turns the dropped items into plain paths or image bytes: turns the dropped items into plain paths or image bytes:
```dart ```dart
DropTarget( DropTarget(
onDragEntered: (d) => setState(() => dragging = true), onDragEntered: (d) => setState(() => dragging = true),
onDragExited: (d) => setState(() => dragging = false), onDragExited: (d) => setState(() => dragging = false),
onDragDone: (details) async { onDragDone: (details) async {
final paths = DesktopDropX.pathsOf(details.files); // absolute paths final paths = DesktopDropX.pathsOf(details.files); // absolute paths
final Uint8List? image = await DesktopDropX.readImage(details.files); final Uint8List? image = await DesktopDropX.readImage(details.files);
// image == null when the drop contains no readable image // image == null when the drop contains no readable image
}, },
child: myWidget, child: myWidget,
) )
``` ```
Platform notes: Platform notes:
- **Linux**: GTK drag destination on the whole window (`text/uri-list` from - **Linux**: GTK drag destination on the whole window (`text/uri-list` from
GNOME/KDE file managers, browsers, …). Items are absolute paths, delivered GNOME/KDE file managers, browsers, …). Items are absolute paths, delivered
in the order dropped. in the order dropped.
- **Windows**: OLE `IDropTarget` on the window (`CF_HDROP` from Explorer, - **Windows**: OLE `IDropTarget` on the window (`CF_HDROP` from Explorer,
browsers, …). Same path semantics; `DropDoneDetails.localPosition` is browsers, …). Same path semantics; `DropDoneDetails.localPosition` is
already DPI-corrected. already DPI-corrected.
- **Android/iOS/web**: no native handler — the widget renders its child and - **Android/iOS/web**: no native handler — the widget renders its child and
no drop events ever arrive. **macOS** is not vendored (no macOS target); no drop events ever arrive. **macOS** is not vendored (no macOS target);
re-adding it is a copy of upstream's `macos/` folder behind the same API. re-adding it is a copy of upstream's `macos/` folder behind the same API.
- `DropItem` extends `XFile`: `item.path`, `item.name`, `item.readAsBytes()`, - `DropItem` extends `XFile`: `item.path`, `item.name`, `item.readAsBytes()`,
`item.openRead()`, … are available directly. `item.openRead()`, … are available directly.
- Drop targets receive events for the **whole window**; an invisible - Drop targets receive events for the **whole window**; an invisible
`DropTarget` keeps receiving them, so set `enable: false` when pushing a `DropTarget` keeps receiving them, so set `enable: false` when pushing a
page over it. Coordinate-based `DropTarget` bounds filtering is handled by page over it. Coordinate-based `DropTarget` bounds filtering is handled by
the widget. the widget.
- `readImage` never throws: unreadable files are logged with a stack trace - `readImage` never throws: unreadable files are logged with a stack trace
and skipped; `null` means "no image in this drop". and skipped; `null` means "no image in this drop".
## Vendored engines ### media_kit hot-restart guard (Linux, debug)
The clipboard channels (`pasteboard` method channel) are **vendored from Apps using media_kit crash on a hot restart (`R`) while any `Player` is
MixinNetwork's `pasteboard` package (v0.5.0, Apache-2.0)** because upstream's alive: mpv calls a wakeup callback that belonged to the discarded isolate.
build applies the legacy Kotlin Gradle Plugin (KGP), which future Flutter fox_utils' Linux plugin clears those callbacks just before the engine
versions will reject. The vendored copy lives in `lib/vendor/` (Dart) and the restarts, so hot restart works. Nothing to call; release builds compile it
native platform folders, with these changes: out; it does nothing without media_kit. It reads media_kit's private
NativeReferenceHolder file (checked against media_kit 1.2.6): re-check after
- **Android**: the Kotlin plugin was ported to Java and merged into media_kit upgrades.
`FoxUtilsPlugin` (one plugin class, no KGP, no Kotlin in the module).
- **Byte-faithful image writes**: upstream decodes and re-encodes images as ## Vendored engines
PNG (destroying GIF animation and metadata); ours writes the original bytes
with magic-byte MIME sniffing. The clipboard channels (`pasteboard` method channel) are **vendored from
- **Merged FileProvider**: declared in the plugin manifest — zero per-project MixinNetwork's `pasteboard` package (v0.5.0, Apache-2.0)** because upstream's
setup (upstream requires a manual manifest entry per app). build applies the legacy Kotlin Gradle Plugin (KGP), which future Flutter
- **Two upstream bugs fixed**: clipboard image reads ran synchronously on the versions will reject. The vendored copy lives in `lib/vendor/` (Dart) and the
calling thread (now on the executor), and `writeFiles` dropped the first native platform folders, with these changes:
clip item (now assembles correctly).
- **Linux/Windows**: C++ sources byte-identical, include paths and CMake - **Android**: the Kotlin plugin was ported to Java and merged into
targets renamed to the `fox_utils` package name. `FoxUtilsPlugin` (one plugin class, no KGP, no Kotlin in the module).
- **Byte-faithful image writes**: upstream decodes and re-encodes images as
To diff against upstream: `git diff` the folders against PNG (destroying GIF animation and metadata); ours writes the original bytes
`packages/pasteboard/` at tag `v0.5.0` of with magic-byte MIME sniffing.
`https://github.com/MixinNetwork/flutter-plugins`. If upstream ever - **Merged FileProvider**: declared in the plugin manifest — zero per-project
modernizes their build, un-vendoring is a swap behind the same `ClipboardX` setup (upstream requires a manual manifest entry per app).
API. - **Two upstream bugs fixed**: clipboard image reads ran synchronously on the
calling thread (now on the executor), and `writeFiles` dropped the first
The drag & drop channels (`desktop_drop` method channel) are **vendored from clip item (now assembles correctly).
MixinNetwork's `desktop_drop` package (v0.7.1, Apache-2.0)** for the same - **Linux/Windows**: C++ sources byte-identical, include paths and CMake
reason: its `android/` folder applies KGP. Only the Dart sources and the targets renamed to the `fox_utils` package name.
Linux/Windows native folders are vendored — the Android/Kotlin, macOS and
web bindings are omitted (Android DnD is out of scope, and there is no macOS To diff against upstream: `git diff` the folders against
target in the app). Linux/Windows native sources are byte-identical to `packages/pasteboard/` at tag `v0.5.0` of
upstream except the include path rename (`include/fox_utils/`). Both engines `https://github.com/MixinNetwork/flutter-plugins`. If upstream ever
are registered from one native entry per platform: modernizes their build, un-vendoring is a swap behind the same `ClipboardX`
`fox_utils_plugin_register_with_registrar` (Linux) / API.
`FoxUtilsPluginRegisterWithRegistrar` (Windows) — the plugin class in
`pubspec.yaml` is no longer `PasteboardPlugin`. To diff against upstream, use The drag & drop channels (`desktop_drop` method channel) are **vendored from
tag `desktop_drop-v0.7.1` of the same monorepo; un-vendoring is a swap MixinNetwork's `desktop_drop` package (v0.7.1, Apache-2.0)** for the same
behind the same `DesktopDropX` API. reason: its `android/` folder applies KGP. Only the Dart sources and the
Linux/Windows native folders are vendored — the Android/Kotlin, macOS and
## License web bindings are omitted (Android DnD is out of scope, and there is no macOS
target in the app). Linux/Windows native sources are byte-identical to
MIT for this package's own code; the vendored engines are Apache-2.0 upstream except the include path rename (`include/fox_utils/`). Both engines
© MixinNetwork (see `lib/vendor/` and the native platform folders). are registered from one native entry per platform:
`fox_utils_plugin_register_with_registrar` (Linux) /
`FoxUtilsPluginRegisterWithRegistrar` (Windows) — the plugin class in
`pubspec.yaml` is no longer `PasteboardPlugin`. To diff against upstream, use
tag `desktop_drop-v0.7.1` of the same monorepo; un-vendoring is a swap
behind the same `DesktopDropX` API.
## License
MIT for this package's own code; the vendored engines are Apache-2.0
© MixinNetwork (see `lib/vendor/` and the native platform folders).
+2
View File
@@ -10,6 +10,7 @@ add_library(${PLUGIN_NAME} SHARED
"fox_utils_plugin.cc" "fox_utils_plugin.cc"
"pasteboard_plugin.cc" "pasteboard_plugin.cc"
"desktop_drop_plugin.cc" "desktop_drop_plugin.cc"
"media_kit_restart_guard.cc"
) )
apply_standard_settings(${PLUGIN_NAME}) apply_standard_settings(${PLUGIN_NAME})
set_target_properties(${PLUGIN_NAME} PROPERTIES set_target_properties(${PLUGIN_NAME} PROPERTIES
@@ -19,6 +20,7 @@ target_include_directories(${PLUGIN_NAME} INTERFACE
"${CMAKE_CURRENT_SOURCE_DIR}/include") "${CMAKE_CURRENT_SOURCE_DIR}/include")
target_link_libraries(${PLUGIN_NAME} PRIVATE flutter) target_link_libraries(${PLUGIN_NAME} PRIVATE flutter)
target_link_libraries(${PLUGIN_NAME} PRIVATE PkgConfig::GTK) target_link_libraries(${PLUGIN_NAME} PRIVATE PkgConfig::GTK)
target_link_libraries(${PLUGIN_NAME} PRIVATE ${CMAKE_DL_LIBS})
# List of absolute paths to libraries that should be bundled with the plugin # List of absolute paths to libraries that should be bundled with the plugin
set(fox_utils_bundled_libraries set(fox_utils_bundled_libraries
+2
View File
@@ -1,8 +1,10 @@
#include "include/fox_utils/fox_utils_plugin.h" #include "include/fox_utils/fox_utils_plugin.h"
#include "include/fox_utils/pasteboard_plugin.h" #include "include/fox_utils/pasteboard_plugin.h"
#include "include/fox_utils/desktop_drop_plugin.h" #include "include/fox_utils/desktop_drop_plugin.h"
#include "include/fox_utils/media_kit_restart_guard.h"
void fox_utils_plugin_register_with_registrar(FlPluginRegistrar* registrar) { void fox_utils_plugin_register_with_registrar(FlPluginRegistrar* registrar) {
pasteboard_plugin_register_with_registrar(registrar); pasteboard_plugin_register_with_registrar(registrar);
desktop_drop_plugin_register_with_registrar(registrar); desktop_drop_plugin_register_with_registrar(registrar);
media_kit_restart_guard_register_with_registrar(registrar);
} }
@@ -0,0 +1,15 @@
// Debug builds only: keeps a Flutter hot restart from crashing apps that use
// media_kit on Linux. Hot restart frees the old isolate's NativeCallables
// while mpv handles stay alive; media_kit then "quit"s the leftovers and mpv
// calls a dead wakeup callback (SIGSEGV on mpv's "core" thread). On the
// engine's on-pre-engine-restart signal this clears every live handle's
// wakeup callback (found through media_kit's NativeReferenceHolder file).
// Does nothing without media_kit/libmpv, and nothing in release builds.
#ifndef FOX_UTILS_MEDIA_KIT_RESTART_GUARD_H_
#define FOX_UTILS_MEDIA_KIT_RESTART_GUARD_H_
#include <flutter_linux/flutter_linux.h>
void media_kit_restart_guard_register_with_registrar(FlPluginRegistrar* registrar);
#endif
+71
View File
@@ -0,0 +1,71 @@
#include "include/fox_utils/media_kit_restart_guard.h"
#ifdef NDEBUG
void media_kit_restart_guard_register_with_registrar(FlPluginRegistrar* registrar) {}
#else
#include <dlfcn.h>
#include <stdint.h>
#include <unistd.h>
namespace {
// Mirrors media_kit's NativeReferenceHolder (debug only): a calloc'd buffer
// of kReferenceBufferSize intptr slots holding live mpv_handle addresses,
// its address written in decimal to <tmp>/<kFilePrefix><pid>.
constexpr int kReferenceBufferSize = 512;
constexpr char kFilePrefix[] = "com.alexmercerind.media_kit.NativeReferenceHolder.";
using SetWakeupCallback = void (*)(void* ctx, void (*cb)(void*), void* d);
SetWakeupCallback find_set_wakeup_callback() {
static const char* const kNames[] = {"libmpv.so.2", "libmpv.so", "libmpv.so.1"};
for (const char* name : kNames) {
void* lib = dlopen(name, RTLD_NOW | RTLD_NOLOAD);
if (lib == nullptr) continue;
auto fn = reinterpret_cast<SetWakeupCallback>(
dlsym(lib, "mpv_set_wakeup_callback"));
dlclose(lib); // Drops only the reference NOLOAD added.
if (fn != nullptr) return fn;
}
return nullptr;
}
// Runs on the engine's UI thread before the old isolate shuts down, so the
// buffer is not being written concurrently. Clearing the callback takes the
// same lock mpv calls it under: once this returns, mpv never calls it again.
// The handles stay in the buffer: media_kit's startup in the new isolate
// still sends them "quit".
void on_pre_engine_restart(FlEngine* engine, gpointer user_data) {
g_autofree gchar* name = g_strdup_printf("%s%d", kFilePrefix, getpid());
g_autofree gchar* path = g_build_filename(g_get_tmp_dir(), name, nullptr);
g_autofree gchar* contents = nullptr;
if (!g_file_get_contents(path, &contents, nullptr, nullptr)) return;
const guint64 address = g_ascii_strtoull(contents, nullptr, 10);
if (address == 0) return;
const SetWakeupCallback set_wakeup = find_set_wakeup_callback();
if (set_wakeup == nullptr) return;
const auto* slots = reinterpret_cast<const intptr_t*>(address);
int cleared = 0;
for (int i = 0; i < kReferenceBufferSize; i++) {
if (slots[i] == 0) continue;
set_wakeup(reinterpret_cast<void*>(slots[i]), nullptr, nullptr);
cleared++;
}
if (cleared > 0) {
g_message("hot restart: unhooked %d mpv wakeup callback(s)", cleared);
}
}
} // namespace
void media_kit_restart_guard_register_with_registrar(FlPluginRegistrar* registrar) {
FlView* view = fl_plugin_registrar_get_view(registrar);
if (view == nullptr) return; // headless: no hot restart to guard
g_signal_connect(fl_view_get_engine(view), "on-pre-engine-restart",
G_CALLBACK(on_pre_engine_restart), nullptr);
}
#endif
+1 -1
View File
@@ -1,6 +1,6 @@
name: fox_utils name: fox_utils
description: A collection of reusable Flutter widgets and extensions. description: A collection of reusable Flutter widgets and extensions.
version: 0.1.7 version: 0.1.9
environment: environment:
sdk: ^3.9.2 sdk: ^3.9.2
+6 -9
View File
@@ -19,15 +19,12 @@ target_include_directories(${PLUGIN_NAME} INTERFACE
"${CMAKE_CURRENT_SOURCE_DIR}/include") "${CMAKE_CURRENT_SOURCE_DIR}/include")
target_link_libraries(${PLUGIN_NAME} PRIVATE flutter flutter_wrapper_plugin) target_link_libraries(${PLUGIN_NAME} PRIVATE flutter flutter_wrapper_plugin)
# GdiPlus doesn't compile with /W4 warning level; strict flags are scoped to # GdiPlus doesn't compile with /W4 (the template's level): all sources use
# this package's own sources. The vendored desktop_drop engine is compiled # /W3, one option per list entry ("/W3 /WX" in one entry reaches cl.exe as a
# as upstream ships it (no warnings-as-errors) since it is not ours to churn. # single bad argument, D8021). No warnings-as-errors: a newer MSVC's new
set_source_files_properties("fox_utils_plugin.cpp" "pasteboard_plugin.cpp" # warning must not break app releases.
PROPERTIES COMPILE_OPTIONS set_source_files_properties("fox_utils_plugin.cpp" "pasteboard_plugin.cpp" "desktop_drop_plugin.cpp"
"$<$<CXX_COMPILER_ID:MSVC>:/W3 /WX>;$<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-Wall -Wextra -Wpedantic -Werror>") PROPERTIES COMPILE_OPTIONS "/W3")
set_source_files_properties("desktop_drop_plugin.cpp"
PROPERTIES COMPILE_OPTIONS
"$<$<CXX_COMPILER_ID:MSVC>:/W3>")
# List of absolute paths to libraries that should be bundled with the plugin # List of absolute paths to libraries that should be bundled with the plugin
set(fox_utils_bundled_libraries set(fox_utils_bundled_libraries