Some native clipboard support for some platforms

I was hoping SDL2 would get this functionality eventually, but nope, proper clipboard support is staged for SDL3, which we're not going to see much of for at least a few more months. This will have to do for 98.0. The feature can be disabled at runtime from powder.pref.

Implementation status:

 - windows (via winapi): has the most friendly api so of course the implementation is flawless and uses every available optimization >_>
 - macos (via cocoa): I'm bad at cocoa so this is only as good as absolutely necessary; TODO: on-demand rendering
 - x11 (via xclip): I am NOT implementing icccm2; TODO: remove reliance on external tools
 - wayland (via wl-clipboard): oh god wayland oh why, you're almost as bad as x11; TODO: remove reliance on external tools
 - android: TODO; is there even a point?
 - emscripten: TODO; the tricky bit is that in a browser we can only get clipboard data when the user is giving it to us, so this will require some JS hackery that I'm not mentally prepared for right now; also I think the supported content types are very limited and you can't just define your own

x11 and wayland support are handled by a common backend which delegates clipboard management to xclip-like external programs, such as xclip itself or wl-clipboard, and can load custom command line templates from powder.pref for use with other such programs.
This commit is contained in:
Tamás Bálint Misius 2023-05-11 17:47:25 +02:00
parent 2eee738a9f
commit fb9cba0d01
No known key found for this signature in database
GPG Key ID: 5B472A12F6ECA9F2
20 changed files with 836 additions and 15 deletions

View File

@ -1,6 +1,7 @@
[binaries]
c = [ 'clang', '-arch', 'arm64' ]
cpp = [ 'clang++', '-arch', 'arm64' ]
objcpp = [ 'clang++', '-arch', 'arm64' ]
strip = 'strip'
[host_machine]

View File

@ -1,6 +1,7 @@
[binaries]
c = [ 'clang', '-arch', 'arm64' ]
cpp = [ 'clang++', '-arch', 'arm64' ]
objcpp = [ 'clang++', '-arch', 'arm64' ]
strip = 'strip'
[host_machine]

View File

@ -362,6 +362,7 @@ else
endif
data_files = []
powder_deps = []
subdir('src')
subdir('resources')
@ -377,7 +378,7 @@ if host_platform == 'emscripten'
endif
if get_option('build_powder')
powder_deps = [
powder_deps += [
threads_dep,
zlib_dep,
png_dep,

View File

@ -280,3 +280,9 @@ option(
value: '',
description: 'Build date string, used by ghactions workflows, not useful otherwise'
)
option(
'platform_clipboard',
type: 'boolean',
value: true,
description: 'Enable platform clipboard, allows copying simulation data between different windows'
)

View File

@ -5,6 +5,7 @@
#include "gui/interface/Engine.h"
#include "graphics/Graphics.h"
#include "common/platform/Platform.h"
#include "common/clipboard/Clipboard.h"
#include <iostream>
int desktopWidth = 1280;
@ -113,6 +114,7 @@ void SDLOpen()
fprintf(stderr, "Initializing SDL (video subsystem): %s\n", SDL_GetError());
Platform::Exit(-1);
}
Clipboard::Init();
SDLSetScreen();
@ -256,6 +258,7 @@ void SDLSetScreen()
Platform::Exit(-1);
}
SDL_RaiseWindow(sdl_window);
Clipboard::RecreateWindow();
}
SDL_RenderSetIntegerScale(sdl_renderer, newFrameOpsNorm.forceIntegerScaling ? SDL_TRUE : SDL_FALSE);
if (!(newFrameOpsNorm.resizable && SDL_GetWindowFlags(sdl_window) & SDL_WINDOW_MAXIMIZED))

View File

@ -0,0 +1,14 @@
#pragma once
#include <memory>
#include "common/String.h"
class GameSave;
namespace Clipboard
{
const ByteString clipboardFormatName = "application/vnd.powdertoy.save";
void SetClipboardData(std::unique_ptr<GameSave> data);
const GameSave *GetClipboardData();
void Init();
void RecreateWindow();
}

View File

@ -0,0 +1,10 @@
#ifdef CLIPBOARD_IMPLS_DECLARE
# define IMPL_DEFINE(subsystem, factory) std::unique_ptr<ClipboardImpl> factory();
#endif
#ifdef CLIPBOARD_IMPLS_DEFINE
# define IMPL_DEFINE(subsystem, factory) { subsystem, factory },
#endif
@impl_defs@
#undef IMPL_DEFINE

View File

@ -0,0 +1,71 @@
#include "Dynamic.h"
#include "Clipboard.h"
#include <Cocoa/Cocoa.h>
#include <iostream>
namespace Clipboard
{
static int changeCount = -1;
class CocoaClipboardImpl : public ClipboardImpl
{
public:
void SetClipboardData() final override
{
@autoreleasepool
{
NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
NSString *format = [NSString stringWithUTF8String:clipboardFormatName.c_str()];
changeCount = [pasteboard declareTypes:[NSArray arrayWithObject:format] owner:nil];
std::vector<char> saveData;
SerializeClipboard(saveData);
const auto *base = &saveData[0];
auto size = saveData.size();
NSData *data = [NSData dataWithBytes:base length:size];
if (![pasteboard setData:data forType:format])
{
std::cerr << "cannot put save on clipboard: [pasteboard setData] failed" << std::endl;
return;
}
}
std::cerr << "put save on clipboard" << std::endl;
}
GetClipboardDataResult GetClipboardData() final override
{
GetClipboardDataChanged gdc;
@autoreleasepool
{
NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
int newChangeCount = [pasteboard changeCount];
if (changeCount == newChangeCount)
{
return GetClipboardDataUnchanged{};
}
changeCount = newChangeCount;
NSString *format = [NSString stringWithUTF8String:clipboardFormatName.c_str()];
NSString *available = [pasteboard availableTypeFromArray:[NSArray arrayWithObject:format]];
if (![available isEqualToString:format])
{
std::cerr << "not getting save from clipboard: no data" << std::endl;
return GetClipboardDataFailed{};
}
NSData *data = [pasteboard dataForType:format];
if (data == nil)
{
std::cerr << "not getting save from clipboard: [pasteboard dataForType] failed" << std::endl;
return GetClipboardDataFailed{};
}
auto *base = reinterpret_cast<const char *>([data bytes]);
auto size = [data length];
gdc.data = std::vector<char>(base, base + size);
}
return gdc;
}
};
std::unique_ptr<ClipboardImpl> CocoaClipboardFactory()
{
return std::make_unique<CocoaClipboardImpl>();
}
}

View File

@ -0,0 +1,120 @@
#include "Dynamic.h"
#include "client/GameSave.h"
#include "prefs/GlobalPrefs.h"
#include "PowderToySDL.h"
#include <SDL_syswm.h>
#include <iostream>
namespace Clipboard
{
#define CLIPBOARD_IMPLS_DECLARE
#include "ClipboardImpls.h"
#undef CLIPBOARD_IMPLS_DECLARE
struct ClipboardImplEntry
{
SDL_SYSWM_TYPE subsystem;
std::unique_ptr<ClipboardImpl> (*factory)();
} clipboardImpls[] = {
#define CLIPBOARD_IMPLS_DEFINE
#include "ClipboardImpls.h"
#undef CLIPBOARD_IMPLS_DEFINE
{ SDL_SYSWM_UNKNOWN, nullptr },
};
std::unique_ptr<GameSave> clipboardData;
std::unique_ptr<ClipboardImpl> clipboard;
void InvokeClipboardSetClipboardData()
{
if (clipboard)
{
if (clipboardData)
{
clipboard->SetClipboardData(); // this either works or it doesn't, we don't care
}
else
{
std::cerr << "cannot put save on clipboard: no data to transfer" << std::endl;
}
}
}
void SerializeClipboard(std::vector<char> &saveData)
{
std::tie(std::ignore, saveData) = clipboardData->Serialise();
}
void SetClipboardData(std::unique_ptr<GameSave> data)
{
clipboardData = std::move(data);
InvokeClipboardSetClipboardData();
}
void InvokeClipboardGetClipboardData()
{
if (clipboard)
{
auto result = clipboard->GetClipboardData();
if (std::holds_alternative<ClipboardImpl::GetClipboardDataUnchanged>(result))
{
std::cerr << "not getting save from clipboard, data unchanged" << std::endl;
return;
}
if (std::holds_alternative<ClipboardImpl::GetClipboardDataUnknown>(result))
{
return;
}
clipboardData.reset();
auto *data = std::get_if<ClipboardImpl::GetClipboardDataChanged>(&result);
if (!data)
{
return;
}
try
{
clipboardData = std::make_unique<GameSave>(data->data);
}
catch (const ParseException &e)
{
std::cerr << "got bad save from clipboard: " << e.what() << std::endl;
return;
}
std::cerr << "got save from clipboard" << std::endl;
}
}
const GameSave *GetClipboardData()
{
InvokeClipboardGetClipboardData();
return clipboardData.get();
}
void Init()
{
}
int currentSubsystem;
void RecreateWindow()
{
// old window is gone (or doesn't exist), associate clipboard data with the new one
SDL_SysWMinfo info;
SDL_VERSION(&info.version);
SDL_GetWindowWMInfo(sdl_window, &info);
clipboard.reset();
currentSubsystem = info.subsystem;
if (GlobalPrefs::Ref().Get<bool>("NativeClipboard.Enabled", true))
{
for (auto *impl = clipboardImpls; impl->factory; ++impl)
{
if (impl->subsystem == currentSubsystem)
{
clipboard = impl->factory();
break;
}
}
}
InvokeClipboardSetClipboardData();
}
}

View File

@ -0,0 +1,45 @@
#pragma once
#include "common/String.h"
#include <vector>
#include <variant>
#include <memory>
class GameSave;
namespace Clipboard
{
class ClipboardImpl
{
public:
virtual ~ClipboardImpl() = default;
virtual void SetClipboardData() = 0;
struct GetClipboardDataUnchanged
{
};
struct GetClipboardDataChanged
{
std::vector<char> data;
};
struct GetClipboardDataFailed
{
};
struct GetClipboardDataUnknown
{
};
using GetClipboardDataResult = std::variant<
GetClipboardDataUnchanged,
GetClipboardDataChanged,
GetClipboardDataFailed,
GetClipboardDataUnknown
>;
virtual GetClipboardDataResult GetClipboardData() = 0;
};
extern std::unique_ptr<GameSave> clipboardData;
void SerializeClipboard(std::vector<char> &saveData);
extern int currentSubsystem;
}

View File

@ -0,0 +1,232 @@
#include "Dynamic.h"
#include "Clipboard.h"
#include "client/GameSave.h"
#include "prefs/GlobalPrefs.h"
#include "PowderToySDL.h"
#include <iostream>
#include <stdio.h>
#include <optional>
#include <algorithm>
#include <signal.h>
#include <map>
#include <SDL_syswm.h>
namespace Clipboard
{
struct Preset
{
ByteString inCommand;
ByteString formatsCommand;
ByteString outCommand;
std::optional<int> defaultForSubsystem;
};
std::map<ByteString, Preset> builtInPresets = {
{ "xclip", {
"xclip -selection clipboard -target %s",
"xclip -out -selection clipboard -target TARGETS",
"xclip -out -selection clipboard -target %s",
SDL_SYSWM_X11,
} },
{ "wl-clipboard", {
"wl-copy --type %s",
"wl-paste --list-types",
"wl-paste --type %s",
SDL_SYSWM_WAYLAND,
} },
};
static ByteString SubstFormat(ByteString str)
{
if (auto split = str.SplitBy("%s"))
{
str = split.Before() + clipboardFormatName + split.After();
}
return str;
}
static std::optional<Preset> GetPreset()
{
std::optional<ByteString> name = GlobalPrefs::Ref().Get("NativeClipboard.External.Type", ByteString("auto"));
if (name == "custom")
{
auto getCommand = [](ByteString key) -> std::optional<ByteString> {
auto fullKey = "NativeClipboard.External." + key;
auto value = GlobalPrefs::Ref().Get<ByteString>(fullKey);
if (!value)
{
std::cerr << "custom external clipboard command preset: missing " << fullKey << std::endl;
return std::nullopt;
}
return *value;
};
auto inCommand = getCommand("In");
auto formatsCommand = getCommand("Formats");
auto outCommand = getCommand("Out");
if (!inCommand || !formatsCommand || !outCommand)
{
return std::nullopt;
}
return Preset{
SubstFormat(*inCommand),
SubstFormat(*formatsCommand),
SubstFormat(*outCommand),
};
}
if (name == "auto")
{
name.reset();
for (auto &[ presetName, preset ] : builtInPresets)
{
if (preset.defaultForSubsystem && *preset.defaultForSubsystem == currentSubsystem)
{
name = presetName;
}
}
if (!name)
{
std::cerr << "no built-in external clipboard command preset for SDL window subsystem " << currentSubsystem << std::endl;
return std::nullopt;
}
}
auto it = builtInPresets.find(*name);
if (it == builtInPresets.end())
{
std::cerr << "no built-in external clipboard command preset with name " << *name << std::endl;
return std::nullopt;
}
return Preset{
SubstFormat(it->second.inCommand),
SubstFormat(it->second.formatsCommand),
SubstFormat(it->second.outCommand),
};
}
class ExternalClipboardImpl : public ClipboardImpl
{
public:
ExternalClipboardImpl()
{
signal(SIGPIPE, SIG_IGN); // avoids problems with popen
}
void SetClipboardData() final override
{
auto preset = GetPreset();
if (!preset)
{
return;
}
auto handle = popen(preset->inCommand.c_str(), "we");
if (!handle)
{
std::cerr << "failed to set clipboard data: popen: " << strerror(errno) << std::endl;
return;
}
auto bail = false;
std::vector<char> saveData;
SerializeClipboard(saveData);
if (fwrite(&saveData[0], 1, saveData.size(), handle) != saveData.size())
{
std::cerr << "failed to set clipboard data: fwrite: " << strerror(errno) << std::endl;
bail = true;
}
auto status = pclose(handle);
if (bail)
{
return;
}
if (status == -1)
{
std::cerr << "failed to set clipboard data: pclose: " << strerror(errno) << std::endl;
return;
}
if (status)
{
std::cerr << "failed to set clipboard data: " << preset->inCommand << ": wait4 status code " << status << std::endl;
return;
}
}
GetClipboardDataResult GetClipboardData() final override
{
auto getTarget = [](ByteString command) -> std::optional<std::vector<char>> {
if (!command.size())
{
return std::nullopt;
}
auto handle = popen(command.c_str(), "re");
if (!handle)
{
std::cerr << "cannot get save from clipboard: popen: " << strerror(errno) << std::endl;
return std::nullopt;
}
constexpr auto blockSize = 0x10000;
std::vector<char> data;
auto bail = false;
while (true)
{
auto pos = data.size();
data.resize(pos + blockSize);
auto got = fread(&data[pos], 1, blockSize, handle);
if (got != blockSize)
{
if (ferror(handle))
{
std::cerr << "cannot get save from clipboard: fread: " << strerror(errno) << std::endl;
bail = true;
break;
}
if (feof(handle))
{
data.resize(data.size() - blockSize + got);
break;
}
}
}
auto status = pclose(handle);
if (bail)
{
return std::nullopt;
}
if (status == -1)
{
std::cerr << "cannot get save from clipboard: pclose: " << strerror(errno) << std::endl;
return std::nullopt;
}
if (status)
{
std::cerr << "cannot get save from clipboard: " << command << ": wait4 status code " << status << std::endl;
return std::nullopt;
}
return data;
};
auto preset = GetPreset();
if (!preset)
{
return GetClipboardDataUnknown{};
}
auto formatsOpt = getTarget(preset->formatsCommand);
if (!formatsOpt)
{
return GetClipboardDataUnknown{};
}
auto formats = ByteString(formatsOpt->begin(), formatsOpt->end()).PartitionBy('\n');
if (std::find(formats.begin(), formats.end(), clipboardFormatName) == formats.end())
{
std::cerr << "not getting save from clipboard: no data" << std::endl;
return GetClipboardDataFailed{};
}
auto saveDataOpt = getTarget(preset->outCommand);
if (!saveDataOpt)
{
return GetClipboardDataFailed{};
}
return GetClipboardDataChanged{ std::move(*saveDataOpt) };
}
};
std::unique_ptr<ClipboardImpl> ExternalClipboardFactory()
{
return std::make_unique<ExternalClipboardImpl>();
}
}

View File

@ -0,0 +1,22 @@
#include "Clipboard.h"
#include "client/GameSave.h"
namespace Clipboard
{
void SetClipboardData(std::unique_ptr<GameSave> data)
{
}
const GameSave *GetClipboardData()
{
return nullptr;
}
void Init()
{
}
void RecreateWindow()
{
}
}

View File

@ -0,0 +1,226 @@
#include "Dynamic.h"
#include "Clipboard.h"
#include "client/GameSave.h"
#include "common/platform/Platform.h"
#include "PowderToySDL.h"
#include <SDL.h>
#include <SDL_syswm.h>
#include <iostream>
#include <windows.h>
namespace Clipboard
{
class WindowsClipboardImpl : public ClipboardImpl
{
UINT saveClipboardFormat = 0;
HWND ourHwnd = NULL;
DWORD seqNumber = 0; // 0 is invalid
class ClipboardSession
{
bool open = false;
public:
ClipboardSession(HWND ourHwnd)
{
if (ourHwnd)
{
open = ::OpenClipboard(ourHwnd);
}
}
~ClipboardSession()
{
if (open)
{
::CloseClipboard();
}
}
explicit operator bool() const
{
return open;
}
};
void Transfer()
{
if (!saveClipboardFormat)
{
std::cerr << "cannot transfer save data: save clipboard format not registered" << std::endl;
return;
}
if (!clipboardData)
{
std::cerr << "cannot transfer save data: no data to transfer" << std::endl;
return;
}
std::vector<char> saveData;
SerializeClipboard(saveData);
auto handle = std::unique_ptr<void, decltype(&::GlobalFree)>(::GlobalAlloc(GMEM_MOVEABLE, saveData.size()), GlobalFree);
if (!handle)
{
std::cerr << "cannot transfer save data: GlobalAlloc failed: " << ::GetLastError() << std::endl;
return;
}
{
auto data = std::unique_ptr<void, decltype(&::GlobalUnlock)>(::GlobalLock(handle.get()), ::GlobalUnlock);
auto base = reinterpret_cast<char *>(data.get());
std::copy(saveData.begin(), saveData.end(), base);
}
if (!::SetClipboardData(saveClipboardFormat, handle.get()))
{
std::cerr << "cannot transfer save data: SetClipboardData failed: " << ::GetLastError() << std::endl;
return;
}
handle.release(); // windows owns it now
auto newSeqNumber = ::GetClipboardSequenceNumber();
if (newSeqNumber)
{
seqNumber = newSeqNumber;
}
std::cerr << "transferred save data" << std::endl;
}
static int TransferWatchWrapper(void *userdata, SDL_Event *event)
{
return reinterpret_cast<WindowsClipboardImpl *>(userdata)->TransferWatch(event);
}
int TransferWatch(SDL_Event *event)
{
// SDL documentation says we have to be very careful with what we do here because
// the callback can come from any random thread, and we indeed are: WM_RENDERFORMAT
// and WM_RENDERALLFORMATS are only posted to windows that have announced data on
// the clipboard, and only our main thread ever owns a window, so we don't touch
// the WindowsClipboardImpl outside of these events.
switch (event->type)
{
case SDL_SYSWMEVENT:
switch (event->syswm.msg->msg.win.msg)
{
case WM_RENDERFORMAT:
if (event->syswm.msg->msg.win.wParam == saveClipboardFormat)
{
Transfer();
}
break;
case WM_RENDERALLFORMATS:
{
ClipboardSession cs(ourHwnd);
if (cs)
{
if (ourHwnd && ::GetClipboardOwner() == ourHwnd)
{
Transfer();
}
}
else
{
std::cerr << "cannot place save on clipboard: OpenClipboard failed: " << ::GetLastError() << std::endl;
}
}
break;
}
break;
}
return 0;
}
public:
WindowsClipboardImpl()
{
SDL_SysWMinfo info;
SDL_VERSION(&info.version);
SDL_GetWindowWMInfo(sdl_window, &info);
ourHwnd = info.info.win.window;
saveClipboardFormat = ::RegisterClipboardFormatW(Platform::WinWiden(clipboardFormatName).c_str());
if (!saveClipboardFormat)
{
std::cerr << "cannot register save clipboard format: RegisterClipboardFormatW failed: " << ::GetLastError() << std::endl;
return;
}
std::cerr << "save clipboard format registered" << std::endl;
SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE);
SDL_AddEventWatch(&WindowsClipboardImpl::TransferWatchWrapper, this);
}
~WindowsClipboardImpl()
{
SDL_DelEventWatch(&WindowsClipboardImpl::TransferWatchWrapper, this);
SDL_EventState(SDL_SYSWMEVENT, SDL_DISABLE);
}
void SetClipboardData() final override
{
if (!saveClipboardFormat)
{
std::cerr << "cannot announce save on clipboard: save clipboard format not registered" << std::endl;
return;
}
ClipboardSession cs(ourHwnd);
if (!cs)
{
std::cerr << "cannot announce save on clipboard: OpenClipboard failed: " << ::GetLastError() << std::endl;
return;
}
if (!::EmptyClipboard())
{
std::cerr << "cannot announce save on clipboard: EmptyClipboard failed: " << ::GetLastError() << std::endl;
return;
}
::SetClipboardData(saveClipboardFormat, NULL);
std::cerr << "announced save on clipboard" << std::endl;
}
GetClipboardDataResult GetClipboardData() final override
{
// Note that the data from the local clipboard is left alone if any error occurs so
// the local clipboard keeps working even in the worst case.
if (!saveClipboardFormat)
{
std::cerr << "cannot get save from clipboard: save clipboard format not registered" << std::endl;
return GetClipboardDataUnknown{};
}
ClipboardSession cs(ourHwnd);
if (!cs)
{
std::cerr << "cannot get save from clipboard: OpenClipboard failed: " << ::GetLastError() << std::endl;
return GetClipboardDataUnknown{};
}
auto newSeqNumber = ::GetClipboardSequenceNumber();
if (seqNumber && newSeqNumber && seqNumber == newSeqNumber)
{
std::cerr << "not getting save from clipboard, data unchanged" << std::endl;
return GetClipboardDataUnchanged{};
}
seqNumber = newSeqNumber;
if (!::IsClipboardFormatAvailable(saveClipboardFormat))
{
std::cerr << "not getting save from clipboard: no data" << std::endl;
return GetClipboardDataFailed{};
}
auto handle = ::GetClipboardData(saveClipboardFormat);
if (!handle)
{
std::cerr << "cannot get save from clipboard: GetClipboardData failed: " << ::GetLastError() << std::endl;
return GetClipboardDataFailed{};
}
auto size = ::GlobalSize(handle);
auto data = std::unique_ptr<void, decltype(&::GlobalUnlock)>(::GlobalLock(handle), ::GlobalUnlock);
if (!data)
{
std::cerr << "cannot get save from clipboard: GlobalLock failed: " << ::GetLastError() << std::endl;
return GetClipboardDataFailed{};
}
auto base = reinterpret_cast<const char *>(data.get());
return GetClipboardDataChanged{ std::vector<char>(base, base + size) };
}
};
std::unique_ptr<ClipboardImpl> WindowsClipboardFactory()
{
return std::make_unique<WindowsClipboardImpl>();
}
}

View File

@ -0,0 +1,39 @@
if get_option('platform_clipboard')
clipboard_impl_factories = []
if host_platform == 'windows'
powder_files += files('Windows.cpp')
clipboard_impl_factories += [
[ 'SDL_SYSWM_WINDOWS', 'WindowsClipboardFactory' ],
]
elif host_platform == 'darwin'
if get_option('build_powder')
add_languages('objcpp', native: false)
powder_deps += [
dependency('Cocoa'),
]
endif
powder_files += files([
'Cocoa.mm',
])
clipboard_impl_factories += [
[ 'SDL_SYSWM_COCOA', 'CocoaClipboardFactory' ],
]
elif host_platform == 'android'
# TODO
elif host_platform == 'emscripten'
# TODO
else
powder_files += files([
'External.cpp',
])
clipboard_impl_factories += [
[ 'SDL_SYSWM_X11', 'ExternalClipboardFactory' ],
[ 'SDL_SYSWM_WAYLAND', 'ExternalClipboardFactory' ],
]
endif
powder_files += files('Dynamic.cpp')
else
powder_files += files('Null.cpp')
endif
render_files += files('Null.cpp')
font_files += files('Null.cpp')

View File

@ -4,4 +4,5 @@ common_files += files(
'tpt-thread-local.cpp',
)
subdir('clipboard')
subdir('platform')

View File

@ -19,6 +19,7 @@
#include "client/SaveInfo.h"
#include "client/http/ExecVoteRequest.h"
#include "common/platform/Platform.h"
#include "common/clipboard/Clipboard.h"
#include "graphics/Renderer.h"
#include "simulation/Air.h"
#include "simulation/GOLString.h"
@ -1371,12 +1372,12 @@ void GameModel::TransformPlaceSave(Mat2<int> transform, Vec2<int> nudge)
void GameModel::SetClipboard(std::unique_ptr<GameSave> save)
{
clipboard = std::move(save);
Clipboard::SetClipboardData(std::move(save));
}
const GameSave *GameModel::GetClipboard() const
{
return clipboard.get();
return Clipboard::GetClipboardData();
}
const GameSave *GameModel::GetTransformedPlaceSave() const

View File

@ -50,9 +50,6 @@ class GameModel
private:
std::vector<Notification*> notifications;
//int clipboardSize;
//unsigned char * clipboardData;
std::unique_ptr<GameSave> clipboard;
std::unique_ptr<GameSave> placeSave;
std::unique_ptr<GameSave> transformedPlaceSave;
std::deque<String> consoleLog;

View File

@ -1656,20 +1656,27 @@ void GameView::OnFileDrop(ByteString filename)
return;
}
auto saveFile = Client::Ref().LoadSaveFile(filename);
if (!saveFile)
return;
if (saveFile->GetError().length())
{
new ErrorMessage("Error loading save", "Dropped save file could not be loaded: " + saveFile->GetError());
return;
}
if (filename.EndsWith(".stm"))
{
auto saveFile = Client::Ref().GetStamp(filename);
if (!saveFile || !saveFile->GetGameSave())
{
new ErrorMessage("Error loading stamp", "Dropped stamp could not be loaded: " + saveFile->GetError());
return;
}
c->LoadStamp(saveFile->TakeGameSave());
}
else
{
auto saveFile = Client::Ref().LoadSaveFile(filename);
if (!saveFile)
return;
if (saveFile->GetError().length())
{
new ErrorMessage("Error loading save", "Dropped save file could not be loaded: " + saveFile->GetError());
return;
}
c->LoadSaveFile(std::move(saveFile));
}

View File

@ -176,6 +176,18 @@ configure_file(
configuration: elements_conf_data
)
clipboard_impl_defs = []
foreach impl_subsys_factory : clipboard_impl_factories
clipboard_impl_defs += 'IMPL_DEFINE(' + impl_subsys_factory[0] + ', ' + impl_subsys_factory[1] + ')'
endforeach
clipboard_impls_data = configuration_data()
clipboard_impls_data.set('impl_defs', '\n'.join(clipboard_impl_defs))
configure_file(
input: 'common/clipboard/ClipboardImpls.template.h',
output: 'ClipboardImpls.h',
configuration: clipboard_impls_data
)
simulation_tool_defs = []
foreach tool_name_id : simulation_tool_ids
simulation_tool_defs += 'TOOL_DEFINE(' + tool_name_id[0] + ', ' + tool_name_id[1].to_string() + ');'

View File

@ -1,6 +1,7 @@
#pragma once
#include "common/String.h"
#include <json/json.h>
#include <optional>
class Prefs
{
@ -35,7 +36,7 @@ public:
Prefs(ByteString path);
template<class Type>
Type Get(ByteString path, Type defaultValue) const
std::optional<Type> Get(ByteString path) const
{
auto value = GetJson(root, path);
if (value != Json::nullValue)
@ -48,6 +49,17 @@ public:
{
}
}
return std::nullopt;
}
template<class Type>
Type Get(ByteString path, Type defaultValue) const
{
auto value = Get<Type>(path);
if (value)
{
return *value;
}
return defaultValue;
}