fox_utils 0.1.7: clipboard, drag & drop, Android file saving
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
dc16bba054
46 files changed
+4738
No files matched your search
@@ -0,0 +1,285 @@
|
||||
#include "include/fox_utils/desktop_drop_plugin.h"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <flutter/method_channel.h>
|
||||
#include <flutter/plugin_registrar_windows.h>
|
||||
#include <flutter/standard_method_codec.h>
|
||||
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <algorithm>
|
||||
|
||||
namespace {
|
||||
|
||||
using FlutterMethodChannel = std::unique_ptr<flutter::MethodChannel<flutter::EncodableValue>>;
|
||||
|
||||
|
||||
using namespace std::literals::string_literals;
|
||||
|
||||
std::string ws2s(const std::wstring &wstr) {
|
||||
if (wstr.empty()) {
|
||||
return {};
|
||||
}
|
||||
size_t pos;
|
||||
size_t begin = 0;
|
||||
std::string ret;
|
||||
|
||||
int size;
|
||||
pos = wstr.find(static_cast<wchar_t>(0), begin);
|
||||
while (pos != std::wstring::npos && begin < wstr.length()) {
|
||||
std::wstring segment = std::wstring(&wstr[begin], pos - begin);
|
||||
size = WideCharToMultiByte(CP_UTF8,
|
||||
WC_ERR_INVALID_CHARS,
|
||||
&segment[0],
|
||||
(int) segment.size(),
|
||||
nullptr,
|
||||
0,
|
||||
nullptr,
|
||||
nullptr);
|
||||
std::string converted = std::string(size, 0);
|
||||
WideCharToMultiByte(CP_UTF8,
|
||||
WC_ERR_INVALID_CHARS,
|
||||
&segment[0],
|
||||
(int) segment.size(),
|
||||
&converted[0],
|
||||
(int) converted.size(),
|
||||
nullptr,
|
||||
nullptr);
|
||||
ret.append(converted);
|
||||
ret.append({0});
|
||||
begin = pos + 1;
|
||||
pos = wstr.find(static_cast<wchar_t>(0), begin);
|
||||
}
|
||||
if (begin <= wstr.length()) {
|
||||
std::wstring segment = std::wstring(&wstr[begin], wstr.length() - begin);
|
||||
size =
|
||||
WideCharToMultiByte(CP_UTF8,
|
||||
WC_ERR_INVALID_CHARS,
|
||||
&segment[0],
|
||||
(int) segment.size(),
|
||||
nullptr,
|
||||
0,
|
||||
nullptr,
|
||||
nullptr);
|
||||
std::string converted = std::string(size, 0);
|
||||
WideCharToMultiByte(CP_UTF8,
|
||||
WC_ERR_INVALID_CHARS,
|
||||
&segment[0],
|
||||
(int) segment.size(),
|
||||
&converted[0],
|
||||
(int) converted.size(),
|
||||
nullptr,
|
||||
nullptr);
|
||||
ret.append(converted);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
class DesktopDropTarget : public IDropTarget {
|
||||
public:
|
||||
|
||||
DesktopDropTarget(FlutterMethodChannel channel, HWND window_handle);
|
||||
|
||||
HRESULT DragEnter(IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) override;
|
||||
|
||||
HRESULT DragOver(DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) override;
|
||||
|
||||
HRESULT DragLeave() override;
|
||||
|
||||
HRESULT Drop(IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) override;
|
||||
|
||||
HRESULT QueryInterface(const IID &riid, void **ppvObject) override;
|
||||
|
||||
ULONG AddRef() override;
|
||||
|
||||
ULONG Release() override;
|
||||
|
||||
virtual ~DesktopDropTarget();
|
||||
|
||||
private:
|
||||
FlutterMethodChannel channel_;
|
||||
HWND window_handle_;
|
||||
LONG ref_count_;
|
||||
bool need_revoke_ole_initialize_;
|
||||
};
|
||||
|
||||
class DesktopDropPlugin : public flutter::Plugin {
|
||||
|
||||
public:
|
||||
static void RegisterWithRegistrar(flutter::PluginRegistrarWindows *registrar);
|
||||
|
||||
explicit DesktopDropPlugin(DesktopDropTarget *target);
|
||||
|
||||
~DesktopDropPlugin() override;
|
||||
|
||||
private:
|
||||
|
||||
DesktopDropTarget *target_;
|
||||
|
||||
};
|
||||
|
||||
DesktopDropPlugin::DesktopDropPlugin(DesktopDropTarget *target) : target_(target) {
|
||||
target_->AddRef();
|
||||
}
|
||||
|
||||
DesktopDropPlugin::~DesktopDropPlugin() {
|
||||
target_->Release();
|
||||
}
|
||||
|
||||
|
||||
// static
|
||||
void DesktopDropPlugin::RegisterWithRegistrar(
|
||||
flutter::PluginRegistrarWindows *registrar) {
|
||||
auto channel =
|
||||
std::make_unique<flutter::MethodChannel<flutter::EncodableValue>>(
|
||||
registrar->messenger(), "desktop_drop",
|
||||
&flutter::StandardMethodCodec::GetInstance());
|
||||
|
||||
HWND hwnd = nullptr;
|
||||
if (registrar->GetView()) {
|
||||
hwnd = registrar->GetView()->GetNativeWindow();
|
||||
}
|
||||
|
||||
if (hwnd == nullptr) {
|
||||
// no window, no drop.
|
||||
return;
|
||||
}
|
||||
|
||||
channel->SetMethodCallHandler([](const auto &call, auto result) {
|
||||
result->NotImplemented();
|
||||
});
|
||||
|
||||
auto drop_target = new DesktopDropTarget(std::move(channel), hwnd);
|
||||
|
||||
auto plugin = std::make_unique<DesktopDropPlugin>(drop_target);
|
||||
|
||||
registrar->AddPlugin(std::move(plugin));
|
||||
}
|
||||
|
||||
|
||||
DesktopDropTarget::DesktopDropTarget(FlutterMethodChannel channel, HWND window_handle) : channel_(
|
||||
std::move(channel)), window_handle_(window_handle), ref_count_(0), need_revoke_ole_initialize_(false) {
|
||||
auto ret = RegisterDragDrop(window_handle_, this);
|
||||
if (ret == E_OUTOFMEMORY) {
|
||||
OleInitialize(nullptr);
|
||||
ret = RegisterDragDrop(window_handle_, this);
|
||||
if (ret == 0) {
|
||||
need_revoke_ole_initialize_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (ret != 0) {
|
||||
std::cout << "RegisterDragDrop failed: " << ret << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
HRESULT DesktopDropTarget::DragEnter(IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) {
|
||||
POINT point = {pt.x, pt.y};
|
||||
ScreenToClient(window_handle_, &point);
|
||||
channel_->InvokeMethod("entered", std::make_unique<flutter::EncodableValue>(
|
||||
flutter::EncodableList{
|
||||
flutter::EncodableValue(double(point.x)),
|
||||
flutter::EncodableValue(double(point.y))
|
||||
}
|
||||
));
|
||||
return 0;
|
||||
}
|
||||
|
||||
HRESULT DesktopDropTarget::DragOver(DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) {
|
||||
POINT point = {pt.x, pt.y};
|
||||
ScreenToClient(window_handle_, &point);
|
||||
channel_->InvokeMethod("updated", std::make_unique<flutter::EncodableValue>(
|
||||
flutter::EncodableList{
|
||||
flutter::EncodableValue(double(point.x)),
|
||||
flutter::EncodableValue(double(point.y))
|
||||
}
|
||||
));
|
||||
return 0;
|
||||
}
|
||||
|
||||
HRESULT DesktopDropTarget::DragLeave() {
|
||||
channel_->InvokeMethod("exited", std::make_unique<flutter::EncodableValue>());
|
||||
return 0;
|
||||
}
|
||||
|
||||
HRESULT DesktopDropTarget::Drop(IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) {
|
||||
|
||||
flutter::EncodableList list = {};
|
||||
|
||||
// construct a FORMATETC object
|
||||
FORMATETC fmtetc = {CF_HDROP, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
|
||||
STGMEDIUM stgmed;
|
||||
|
||||
// See if the dataobject contains any TEXT stored as a HGLOBAL
|
||||
if (pDataObj->QueryGetData(&fmtetc) == S_OK) {
|
||||
// Yippie! the data is there, so go get it!
|
||||
if (pDataObj->GetData(&fmtetc, &stgmed) == S_OK) {
|
||||
// we asked for the data as a HGLOBAL, so access it appropriately
|
||||
PVOID data = GlobalLock(stgmed.hGlobal);
|
||||
if (data != nullptr) {
|
||||
auto files = DragQueryFile(reinterpret_cast<HDROP>(data), 0xFFFFFFFF, nullptr, 0);
|
||||
for (unsigned int i = 0; i < files; ++i) {
|
||||
TCHAR filename[MAX_PATH];
|
||||
DragQueryFile(reinterpret_cast<HDROP>(data), i, filename, sizeof(TCHAR) * MAX_PATH);
|
||||
std::wstring wide(filename);
|
||||
std::string path = ws2s(wide);
|
||||
std::cout << "done: " << path << std::endl;
|
||||
list.push_back(flutter::EncodableValue(path));
|
||||
}
|
||||
GlobalUnlock(stgmed.hGlobal);
|
||||
}
|
||||
|
||||
// release the data using the COM API
|
||||
ReleaseStgMedium(&stgmed);
|
||||
}
|
||||
}
|
||||
channel_->InvokeMethod("performOperation", std::make_unique<flutter::EncodableValue>(list));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
HRESULT DesktopDropTarget::QueryInterface(const IID &iid, void **ppvObject) {
|
||||
if (iid == IID_IDropTarget || iid == IID_IUnknown) {
|
||||
AddRef();
|
||||
*ppvObject = this;
|
||||
return S_OK;
|
||||
} else {
|
||||
*ppvObject = nullptr;
|
||||
return E_NOINTERFACE;
|
||||
}
|
||||
}
|
||||
|
||||
ULONG DesktopDropTarget::AddRef() {
|
||||
return InterlockedIncrement(&ref_count_);
|
||||
}
|
||||
|
||||
ULONG DesktopDropTarget::Release() {
|
||||
LONG count = InterlockedDecrement(&ref_count_);
|
||||
|
||||
if (count == 0) {
|
||||
delete this;
|
||||
return 0;
|
||||
} else {
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
DesktopDropTarget::~DesktopDropTarget() {
|
||||
RevokeDragDrop(window_handle_);
|
||||
if (need_revoke_ole_initialize_) {
|
||||
OleUninitialize();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void DesktopDropPluginRegisterWithRegistrar(
|
||||
FlutterDesktopPluginRegistrarRef registrar) {
|
||||
DesktopDropPlugin::RegisterWithRegistrar(
|
||||
flutter::PluginRegistrarManager::GetInstance()
|
||||
->GetRegistrar<flutter::PluginRegistrarWindows>(registrar));
|
||||
|
||||
}
|
||||
Reference in new issue
Block a user