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:
parent
2eee738a9f
commit
fb9cba0d01
1
.github/macaa64-ghactions.ini
vendored
1
.github/macaa64-ghactions.ini
vendored
@ -1,6 +1,7 @@
|
|||||||
[binaries]
|
[binaries]
|
||||||
c = [ 'clang', '-arch', 'arm64' ]
|
c = [ 'clang', '-arch', 'arm64' ]
|
||||||
cpp = [ 'clang++', '-arch', 'arm64' ]
|
cpp = [ 'clang++', '-arch', 'arm64' ]
|
||||||
|
objcpp = [ 'clang++', '-arch', 'arm64' ]
|
||||||
strip = 'strip'
|
strip = 'strip'
|
||||||
|
|
||||||
[host_machine]
|
[host_machine]
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
[binaries]
|
[binaries]
|
||||||
c = [ 'clang', '-arch', 'arm64' ]
|
c = [ 'clang', '-arch', 'arm64' ]
|
||||||
cpp = [ 'clang++', '-arch', 'arm64' ]
|
cpp = [ 'clang++', '-arch', 'arm64' ]
|
||||||
|
objcpp = [ 'clang++', '-arch', 'arm64' ]
|
||||||
strip = 'strip'
|
strip = 'strip'
|
||||||
|
|
||||||
[host_machine]
|
[host_machine]
|
||||||
|
@ -362,6 +362,7 @@ else
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
data_files = []
|
data_files = []
|
||||||
|
powder_deps = []
|
||||||
|
|
||||||
subdir('src')
|
subdir('src')
|
||||||
subdir('resources')
|
subdir('resources')
|
||||||
@ -377,7 +378,7 @@ if host_platform == 'emscripten'
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
if get_option('build_powder')
|
if get_option('build_powder')
|
||||||
powder_deps = [
|
powder_deps += [
|
||||||
threads_dep,
|
threads_dep,
|
||||||
zlib_dep,
|
zlib_dep,
|
||||||
png_dep,
|
png_dep,
|
||||||
|
@ -280,3 +280,9 @@ option(
|
|||||||
value: '',
|
value: '',
|
||||||
description: 'Build date string, used by ghactions workflows, not useful otherwise'
|
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'
|
||||||
|
)
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
#include "gui/interface/Engine.h"
|
#include "gui/interface/Engine.h"
|
||||||
#include "graphics/Graphics.h"
|
#include "graphics/Graphics.h"
|
||||||
#include "common/platform/Platform.h"
|
#include "common/platform/Platform.h"
|
||||||
|
#include "common/clipboard/Clipboard.h"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
int desktopWidth = 1280;
|
int desktopWidth = 1280;
|
||||||
@ -113,6 +114,7 @@ void SDLOpen()
|
|||||||
fprintf(stderr, "Initializing SDL (video subsystem): %s\n", SDL_GetError());
|
fprintf(stderr, "Initializing SDL (video subsystem): %s\n", SDL_GetError());
|
||||||
Platform::Exit(-1);
|
Platform::Exit(-1);
|
||||||
}
|
}
|
||||||
|
Clipboard::Init();
|
||||||
|
|
||||||
SDLSetScreen();
|
SDLSetScreen();
|
||||||
|
|
||||||
@ -256,6 +258,7 @@ void SDLSetScreen()
|
|||||||
Platform::Exit(-1);
|
Platform::Exit(-1);
|
||||||
}
|
}
|
||||||
SDL_RaiseWindow(sdl_window);
|
SDL_RaiseWindow(sdl_window);
|
||||||
|
Clipboard::RecreateWindow();
|
||||||
}
|
}
|
||||||
SDL_RenderSetIntegerScale(sdl_renderer, newFrameOpsNorm.forceIntegerScaling ? SDL_TRUE : SDL_FALSE);
|
SDL_RenderSetIntegerScale(sdl_renderer, newFrameOpsNorm.forceIntegerScaling ? SDL_TRUE : SDL_FALSE);
|
||||||
if (!(newFrameOpsNorm.resizable && SDL_GetWindowFlags(sdl_window) & SDL_WINDOW_MAXIMIZED))
|
if (!(newFrameOpsNorm.resizable && SDL_GetWindowFlags(sdl_window) & SDL_WINDOW_MAXIMIZED))
|
||||||
|
14
src/common/clipboard/Clipboard.h
Normal file
14
src/common/clipboard/Clipboard.h
Normal 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();
|
||||||
|
}
|
10
src/common/clipboard/ClipboardImpls.template.h
Normal file
10
src/common/clipboard/ClipboardImpls.template.h
Normal 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
|
71
src/common/clipboard/Cocoa.mm
Normal file
71
src/common/clipboard/Cocoa.mm
Normal 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>();
|
||||||
|
}
|
||||||
|
}
|
120
src/common/clipboard/Dynamic.cpp
Normal file
120
src/common/clipboard/Dynamic.cpp
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
45
src/common/clipboard/Dynamic.h
Normal file
45
src/common/clipboard/Dynamic.h
Normal 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;
|
||||||
|
}
|
232
src/common/clipboard/External.cpp
Normal file
232
src/common/clipboard/External.cpp
Normal 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>();
|
||||||
|
}
|
||||||
|
}
|
22
src/common/clipboard/Null.cpp
Normal file
22
src/common/clipboard/Null.cpp
Normal 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()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
226
src/common/clipboard/Windows.cpp
Normal file
226
src/common/clipboard/Windows.cpp
Normal 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>();
|
||||||
|
}
|
||||||
|
}
|
39
src/common/clipboard/meson.build
Normal file
39
src/common/clipboard/meson.build
Normal 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')
|
@ -4,4 +4,5 @@ common_files += files(
|
|||||||
'tpt-thread-local.cpp',
|
'tpt-thread-local.cpp',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
subdir('clipboard')
|
||||||
subdir('platform')
|
subdir('platform')
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
#include "client/SaveInfo.h"
|
#include "client/SaveInfo.h"
|
||||||
#include "client/http/ExecVoteRequest.h"
|
#include "client/http/ExecVoteRequest.h"
|
||||||
#include "common/platform/Platform.h"
|
#include "common/platform/Platform.h"
|
||||||
|
#include "common/clipboard/Clipboard.h"
|
||||||
#include "graphics/Renderer.h"
|
#include "graphics/Renderer.h"
|
||||||
#include "simulation/Air.h"
|
#include "simulation/Air.h"
|
||||||
#include "simulation/GOLString.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)
|
void GameModel::SetClipboard(std::unique_ptr<GameSave> save)
|
||||||
{
|
{
|
||||||
clipboard = std::move(save);
|
Clipboard::SetClipboardData(std::move(save));
|
||||||
}
|
}
|
||||||
|
|
||||||
const GameSave *GameModel::GetClipboard() const
|
const GameSave *GameModel::GetClipboard() const
|
||||||
{
|
{
|
||||||
return clipboard.get();
|
return Clipboard::GetClipboardData();
|
||||||
}
|
}
|
||||||
|
|
||||||
const GameSave *GameModel::GetTransformedPlaceSave() const
|
const GameSave *GameModel::GetTransformedPlaceSave() const
|
||||||
|
@ -50,9 +50,6 @@ class GameModel
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<Notification*> notifications;
|
std::vector<Notification*> notifications;
|
||||||
//int clipboardSize;
|
|
||||||
//unsigned char * clipboardData;
|
|
||||||
std::unique_ptr<GameSave> clipboard;
|
|
||||||
std::unique_ptr<GameSave> placeSave;
|
std::unique_ptr<GameSave> placeSave;
|
||||||
std::unique_ptr<GameSave> transformedPlaceSave;
|
std::unique_ptr<GameSave> transformedPlaceSave;
|
||||||
std::deque<String> consoleLog;
|
std::deque<String> consoleLog;
|
||||||
|
@ -1656,20 +1656,27 @@ void GameView::OnFileDrop(ByteString filename)
|
|||||||
return;
|
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"))
|
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());
|
c->LoadStamp(saveFile->TakeGameSave());
|
||||||
}
|
}
|
||||||
else
|
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));
|
c->LoadSaveFile(std::move(saveFile));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,6 +176,18 @@ configure_file(
|
|||||||
configuration: elements_conf_data
|
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 = []
|
simulation_tool_defs = []
|
||||||
foreach tool_name_id : simulation_tool_ids
|
foreach tool_name_id : simulation_tool_ids
|
||||||
simulation_tool_defs += 'TOOL_DEFINE(' + tool_name_id[0] + ', ' + tool_name_id[1].to_string() + ');'
|
simulation_tool_defs += 'TOOL_DEFINE(' + tool_name_id[0] + ', ' + tool_name_id[1].to_string() + ');'
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "common/String.h"
|
#include "common/String.h"
|
||||||
#include <json/json.h>
|
#include <json/json.h>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
class Prefs
|
class Prefs
|
||||||
{
|
{
|
||||||
@ -35,7 +36,7 @@ public:
|
|||||||
Prefs(ByteString path);
|
Prefs(ByteString path);
|
||||||
|
|
||||||
template<class Type>
|
template<class Type>
|
||||||
Type Get(ByteString path, Type defaultValue) const
|
std::optional<Type> Get(ByteString path) const
|
||||||
{
|
{
|
||||||
auto value = GetJson(root, path);
|
auto value = GetJson(root, path);
|
||||||
if (value != Json::nullValue)
|
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;
|
return defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user