new input system
This commit is contained in:
parent
b3db40ebec
commit
ac2eb7fb0a
31 changed files with 3292 additions and 781 deletions
174
implementations/Crafter.Graphics-Clipboard.cpp
Normal file
174
implementations/Crafter.Graphics-Clipboard.cpp
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
/*
|
||||
Crafter®.Graphics
|
||||
Copyright (C) 2026 Catcrafts®
|
||||
Catcrafts.net
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 3.0 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
module;
|
||||
#ifdef CRAFTER_GRAPHICS_WINDOW_WAYLAND
|
||||
#include <wayland-client.h>
|
||||
#include <wayland-client-protocol.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#endif
|
||||
#ifdef CRAFTER_GRAPHICS_WINDOW_WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#define NOMINMAX
|
||||
#include <windows.h>
|
||||
#endif
|
||||
module Crafter.Graphics;
|
||||
import std;
|
||||
|
||||
using namespace Crafter;
|
||||
|
||||
#ifdef CRAFTER_GRAPHICS_WINDOW_WAYLAND
|
||||
namespace {
|
||||
// Heap-allocated state attached to each wl_data_source via its
|
||||
// user_data slot. The text must outlive the source (the compositor
|
||||
// can call `send` minutes after we set the selection, every time a
|
||||
// remote app pastes), so we own it here and free it from
|
||||
// `OnSourceCancelled` — which is the only callback the compositor
|
||||
// guarantees will fire when the source is no longer needed (when
|
||||
// the selection is replaced, or on app exit).
|
||||
struct Held {
|
||||
std::string text;
|
||||
};
|
||||
|
||||
void OnSourceTarget(void*, wl_data_source*, const char*) {}
|
||||
|
||||
void OnSourceSend(void* data, wl_data_source*,
|
||||
const char* /*mime_type*/, std::int32_t fd) {
|
||||
// We only ever advertise text MIME types, so any negotiated
|
||||
// type maps to the same UTF-8 buffer. `send` may fire multiple
|
||||
// times across the lifetime of one selection (each paste is a
|
||||
// fresh fd), so we must not consume `text` here.
|
||||
Held* h = static_cast<Held*>(data);
|
||||
const char* p = h->text.data();
|
||||
std::size_t rem = h->text.size();
|
||||
while (rem > 0) {
|
||||
const ssize_t w = ::write(fd, p, rem);
|
||||
if (w < 0) {
|
||||
if (errno == EINTR) continue;
|
||||
break; // pipe closed; remote gave up reading
|
||||
}
|
||||
if (w == 0) break;
|
||||
p += w;
|
||||
rem -= static_cast<std::size_t>(w);
|
||||
}
|
||||
::close(fd);
|
||||
}
|
||||
|
||||
void OnSourceCancelled(void* data, wl_data_source* source) {
|
||||
// Selection was replaced (someone else copied) or the app is
|
||||
// exiting. Either way, this is our hook to release the held
|
||||
// text and the source itself.
|
||||
delete static_cast<Held*>(data);
|
||||
wl_data_source_destroy(source);
|
||||
}
|
||||
|
||||
// Drag-and-drop only callbacks. We never start a DnD action, so
|
||||
// these can't fire — but the listener struct's size is fixed at
|
||||
// the v3 shape, and wayland-client uses the struct's slots
|
||||
// directly. Stubs are required.
|
||||
void OnSourceDndDropPerformed(void*, wl_data_source*) {}
|
||||
void OnSourceDndFinished(void*, wl_data_source*) {}
|
||||
void OnSourceAction(void*, wl_data_source*, std::uint32_t) {}
|
||||
|
||||
constexpr wl_data_source_listener kSourceListener = {
|
||||
.target = OnSourceTarget,
|
||||
.send = OnSourceSend,
|
||||
.cancelled = OnSourceCancelled,
|
||||
.dnd_drop_performed = OnSourceDndDropPerformed,
|
||||
.dnd_finished = OnSourceDndFinished,
|
||||
.action = OnSourceAction,
|
||||
};
|
||||
}
|
||||
#endif
|
||||
|
||||
bool Crafter::Clipboard::SetText(std::string_view text) {
|
||||
#ifdef CRAFTER_GRAPHICS_WINDOW_WAYLAND
|
||||
if (Device::dataDeviceManager == nullptr || Device::dataDevice == nullptr) {
|
||||
// Compositor doesn't expose wl_data_device_manager (rare; some
|
||||
// headless / minimal compositors). Caller can fall back.
|
||||
return false;
|
||||
}
|
||||
// wl_data_device.set_selection requires a serial from a recent
|
||||
// input event. We track the most recent pointer-enter serial on
|
||||
// each window — fine for "user clicked Copy" interactions, since
|
||||
// the click itself produced the serial. Without one, the
|
||||
// compositor would silently reject the request.
|
||||
std::uint32_t serial = 0;
|
||||
if (Device::focusedWindow != nullptr) {
|
||||
serial = Device::focusedWindow->lastPointerSerial_;
|
||||
}
|
||||
if (serial == 0) return false;
|
||||
|
||||
auto* held = new Held{ std::string(text) };
|
||||
wl_data_source* source =
|
||||
wl_data_device_manager_create_data_source(Device::dataDeviceManager);
|
||||
if (source == nullptr) {
|
||||
delete held;
|
||||
return false;
|
||||
}
|
||||
wl_data_source_add_listener(source, &kSourceListener, held);
|
||||
|
||||
// Advertise the four common text MIME types so legacy + modern
|
||||
// pasters both find a match. Wayland clients prefer the first one
|
||||
// they recognise, in order.
|
||||
wl_data_source_offer(source, "text/plain;charset=utf-8");
|
||||
wl_data_source_offer(source, "text/plain");
|
||||
wl_data_source_offer(source, "UTF8_STRING");
|
||||
wl_data_source_offer(source, "TEXT");
|
||||
|
||||
wl_data_device_set_selection(Device::dataDevice, source, serial);
|
||||
// Push the request so the compositor sees it before the next event
|
||||
// loop iteration — otherwise a quick read in another app might
|
||||
// miss the selection update.
|
||||
wl_display_flush(Device::display);
|
||||
return true;
|
||||
#elif defined(CRAFTER_GRAPHICS_WINDOW_WIN32)
|
||||
// CF_UNICODETEXT round-trip. Convert UTF-8 → UTF-16, allocate a
|
||||
// moveable HGLOBAL, hand it off to the OS. The OS frees the global
|
||||
// when the next clipboard owner replaces the data, so our caller
|
||||
// doesn't need to keep `text` alive — same lifetime contract as
|
||||
// the Wayland path.
|
||||
if (!OpenClipboard(nullptr)) return false;
|
||||
EmptyClipboard();
|
||||
const int wlen = MultiByteToWideChar(CP_UTF8, 0,
|
||||
text.data(), static_cast<int>(text.size()), nullptr, 0);
|
||||
if (wlen < 0) { CloseClipboard(); return false; }
|
||||
HGLOBAL h = GlobalAlloc(GMEM_MOVEABLE,
|
||||
(static_cast<std::size_t>(wlen) + 1) * sizeof(wchar_t));
|
||||
if (h == nullptr) { CloseClipboard(); return false; }
|
||||
wchar_t* dst = static_cast<wchar_t*>(GlobalLock(h));
|
||||
if (dst == nullptr) {
|
||||
GlobalFree(h);
|
||||
CloseClipboard();
|
||||
return false;
|
||||
}
|
||||
MultiByteToWideChar(CP_UTF8, 0,
|
||||
text.data(), static_cast<int>(text.size()), dst, wlen);
|
||||
dst[wlen] = L'\0';
|
||||
GlobalUnlock(h);
|
||||
const bool ok = SetClipboardData(CF_UNICODETEXT, h) != nullptr;
|
||||
if (!ok) GlobalFree(h); // OS only takes ownership on success
|
||||
CloseClipboard();
|
||||
return ok;
|
||||
#else
|
||||
(void)text;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue