mirror of
https://github.com/aseprite/aseprite.git
synced 2025-04-10 21:44:22 +00:00
Merge branch 'interprocessing'
This commit is contained in:
commit
b9130b83f4
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -69,3 +69,6 @@
|
|||||||
[submodule "third_party/curl"]
|
[submodule "third_party/curl"]
|
||||||
path = third_party/curl
|
path = third_party/curl
|
||||||
url = https://github.com/aseprite/curl.git
|
url = https://github.com/aseprite/curl.git
|
||||||
|
[submodule "third_party/IXWebSocket"]
|
||||||
|
path = third_party/IXWebSocket
|
||||||
|
url = https://github.com/machinezone/IXWebSocket
|
||||||
|
@ -70,6 +70,7 @@ option(ENABLE_MEMLEAK "Enable memory-leaks detector (only for developers)"
|
|||||||
option(ENABLE_NEWS "Enable the news in Home tab" on)
|
option(ENABLE_NEWS "Enable the news in Home tab" on)
|
||||||
option(ENABLE_UPDATER "Enable automatic check for updates" on)
|
option(ENABLE_UPDATER "Enable automatic check for updates" on)
|
||||||
option(ENABLE_SCRIPTING "Compile with scripting support" on)
|
option(ENABLE_SCRIPTING "Compile with scripting support" on)
|
||||||
|
option(ENABLE_WEBSOCKET "Compile with websocket support" on)
|
||||||
option(ENABLE_TESTS "Compile unit tests" off)
|
option(ENABLE_TESTS "Compile unit tests" off)
|
||||||
option(ENABLE_BENCHMARKS "Compile benchmarks" off)
|
option(ENABLE_BENCHMARKS "Compile benchmarks" off)
|
||||||
option(ENABLE_TRIAL_MODE "Compile the trial version" off)
|
option(ENABLE_TRIAL_MODE "Compile the trial version" off)
|
||||||
|
@ -1377,11 +1377,13 @@ title = Security
|
|||||||
script_label = The following script:
|
script_label = The following script:
|
||||||
file_label = wants to access to this file:
|
file_label = wants to access to this file:
|
||||||
command_label = wants to execute the following command:
|
command_label = wants to execute the following command:
|
||||||
|
websocket_label = wants to open a WebSocket connection to this URL:
|
||||||
dont_show_for_this_access = Don't show this specific alert again for this script
|
dont_show_for_this_access = Don't show this specific alert again for this script
|
||||||
dont_show_for_this_script = Give full trust to this script
|
dont_show_for_this_script = Give full trust to this script
|
||||||
allow_execute_access = &Allow Execute Access
|
allow_execute_access = &Allow Execute Access
|
||||||
allow_write_access = &Allow Write Access
|
allow_write_access = &Allow Write Access
|
||||||
allow_read_access = &Allow Read Access
|
allow_read_access = &Allow Read Access
|
||||||
|
allow_open_conn_access = &Allow to Open Connections
|
||||||
give_full_access = Give Script Full &Access
|
give_full_access = Give Script Full &Access
|
||||||
stop_script = &Stop Script
|
stop_script = &Stop Script
|
||||||
|
|
||||||
|
2
laf
2
laf
@ -1 +1 @@
|
|||||||
Subproject commit f58ece8248e2ebf6124b28ec49044be913f57292
|
Subproject commit c9bb18ba92915f0d9735da25360e4725f767b447
|
@ -74,6 +74,11 @@ if(ENABLE_SCRIPTING)
|
|||||||
add_definitions(-DENABLE_SCRIPTING)
|
add_definitions(-DENABLE_SCRIPTING)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if(ENABLE_WEBSOCKET)
|
||||||
|
# Needed for "app" and "main"
|
||||||
|
add_definitions(-DENABLE_WEBSOCKET)
|
||||||
|
endif()
|
||||||
|
|
||||||
######################################################################
|
######################################################################
|
||||||
# Aseprite Libraries (in preferred order to be built)
|
# Aseprite Libraries (in preferred order to be built)
|
||||||
|
|
||||||
|
@ -147,6 +147,10 @@ if(ENABLE_SCRIPTING)
|
|||||||
commands/cmd_open_script_folder.cpp
|
commands/cmd_open_script_folder.cpp
|
||||||
ui/devconsole_view.cpp)
|
ui/devconsole_view.cpp)
|
||||||
endif()
|
endif()
|
||||||
|
if(ENABLE_WEBSOCKET)
|
||||||
|
set(scripting_files_ws
|
||||||
|
script/websocket_class.cpp)
|
||||||
|
endif()
|
||||||
set(scripting_files
|
set(scripting_files
|
||||||
commands/cmd_run_script.cpp
|
commands/cmd_run_script.cpp
|
||||||
script/app_command_object.cpp
|
script/app_command_object.cpp
|
||||||
@ -159,6 +163,7 @@ if(ENABLE_SCRIPTING)
|
|||||||
script/color_space_class.cpp
|
script/color_space_class.cpp
|
||||||
script/dialog_class.cpp
|
script/dialog_class.cpp
|
||||||
script/engine.cpp
|
script/engine.cpp
|
||||||
|
script/events_class.cpp
|
||||||
script/frame_class.cpp
|
script/frame_class.cpp
|
||||||
script/frames_class.cpp
|
script/frames_class.cpp
|
||||||
script/image_class.cpp
|
script/image_class.cpp
|
||||||
@ -190,6 +195,7 @@ if(ENABLE_SCRIPTING)
|
|||||||
script/values.cpp
|
script/values.cpp
|
||||||
script/version_class.cpp
|
script/version_class.cpp
|
||||||
shell.cpp
|
shell.cpp
|
||||||
|
${scripting_files_ws}
|
||||||
${scripting_files_ui})
|
${scripting_files_ui})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
@ -674,6 +680,10 @@ endif()
|
|||||||
|
|
||||||
if(ENABLE_SCRIPTING)
|
if(ENABLE_SCRIPTING)
|
||||||
target_link_libraries(app-lib lua lauxlib lualib)
|
target_link_libraries(app-lib lua lauxlib lualib)
|
||||||
|
|
||||||
|
if(ENABLE_WEBSOCKET)
|
||||||
|
target_link_libraries(app-lib ixwebsocket)
|
||||||
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(ENABLE_UPDATER)
|
if(ENABLE_UPDATER)
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// Aseprite
|
// Aseprite
|
||||||
// Copyright (C) 2019-2020 Igara Studio S.A.
|
// Copyright (C) 2019-2021 Igara Studio S.A.
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
// the End-User License Agreement for Aseprite.
|
// the End-User License Agreement for Aseprite.
|
||||||
@ -28,10 +28,11 @@ ActiveSiteHandler::~ActiveSiteHandler()
|
|||||||
void ActiveSiteHandler::addDoc(Doc* doc)
|
void ActiveSiteHandler::addDoc(Doc* doc)
|
||||||
{
|
{
|
||||||
Data data;
|
Data data;
|
||||||
if (doc::Layer* layer = doc->sprite()->root()->firstLayer())
|
data.layer = doc::NullId;
|
||||||
data.layer = layer->id();
|
if (doc->sprite()) { // The sprite can be nullptr in some tests
|
||||||
else
|
if (doc::Layer* layer = doc->sprite()->root()->firstLayer())
|
||||||
data.layer = doc::NullId;
|
data.layer = layer->id();
|
||||||
|
}
|
||||||
data.frame = 0;
|
data.frame = 0;
|
||||||
m_data.insert(std::make_pair(doc, data));
|
m_data.insert(std::make_pair(doc, data));
|
||||||
doc->add_observer(this);
|
doc->add_observer(this);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// Aseprite
|
// Aseprite
|
||||||
// Copyright (C) 2018-2020 Igara Studio S.A.
|
// Copyright (C) 2018-2021 Igara Studio S.A.
|
||||||
// Copyright (C) 2001-2018 David Capello
|
// Copyright (C) 2001-2018 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
@ -81,7 +81,7 @@ Doc* Context::activeDocument() const
|
|||||||
|
|
||||||
void Context::setActiveDocument(Doc* document)
|
void Context::setActiveDocument(Doc* document)
|
||||||
{
|
{
|
||||||
onSetActiveDocument(document);
|
onSetActiveDocument(document, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Context::setActiveLayer(doc::Layer* layer)
|
void Context::setActiveLayer(doc::Layer* layer)
|
||||||
@ -206,15 +206,19 @@ void Context::onAddDocument(Doc* doc)
|
|||||||
|
|
||||||
if (m_activeSiteHandler)
|
if (m_activeSiteHandler)
|
||||||
m_activeSiteHandler->addDoc(doc);
|
m_activeSiteHandler->addDoc(doc);
|
||||||
|
|
||||||
|
notifyActiveSiteChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Context::onRemoveDocument(Doc* doc)
|
void Context::onRemoveDocument(Doc* doc)
|
||||||
{
|
{
|
||||||
if (doc == m_lastSelectedDoc)
|
|
||||||
m_lastSelectedDoc = nullptr;
|
|
||||||
|
|
||||||
if (m_activeSiteHandler)
|
if (m_activeSiteHandler)
|
||||||
m_activeSiteHandler->removeDoc(doc);
|
m_activeSiteHandler->removeDoc(doc);
|
||||||
|
|
||||||
|
if (doc == m_lastSelectedDoc) {
|
||||||
|
m_lastSelectedDoc = nullptr;
|
||||||
|
notifyActiveSiteChanged();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Context::onGetActiveSite(Site* site) const
|
void Context::onGetActiveSite(Site* site) const
|
||||||
@ -224,24 +228,33 @@ void Context::onGetActiveSite(Site* site) const
|
|||||||
activeSiteHandler()->getActiveSiteForDoc(doc, site);
|
activeSiteHandler()->getActiveSiteForDoc(doc, site);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Context::onSetActiveDocument(Doc* doc)
|
void Context::onSetActiveDocument(Doc* doc, bool notify)
|
||||||
{
|
{
|
||||||
m_lastSelectedDoc = doc;
|
m_lastSelectedDoc = doc;
|
||||||
|
if (notify)
|
||||||
|
notifyActiveSiteChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Context::onSetActiveLayer(doc::Layer* layer)
|
void Context::onSetActiveLayer(doc::Layer* layer)
|
||||||
{
|
{
|
||||||
Doc* newDoc = (layer ? static_cast<Doc*>(layer->sprite()->document()): nullptr);
|
Doc* newDoc = (layer ? static_cast<Doc*>(layer->sprite()->document()): nullptr);
|
||||||
|
if (!newDoc)
|
||||||
|
return;
|
||||||
|
|
||||||
|
activeSiteHandler()->setActiveLayerInDoc(newDoc, layer);
|
||||||
|
|
||||||
if (newDoc != m_lastSelectedDoc)
|
if (newDoc != m_lastSelectedDoc)
|
||||||
setActiveDocument(newDoc);
|
setActiveDocument(newDoc);
|
||||||
if (newDoc)
|
else
|
||||||
activeSiteHandler()->setActiveLayerInDoc(newDoc, layer);
|
notifyActiveSiteChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Context::onSetActiveFrame(const doc::frame_t frame)
|
void Context::onSetActiveFrame(const doc::frame_t frame)
|
||||||
{
|
{
|
||||||
if (m_lastSelectedDoc)
|
if (m_lastSelectedDoc)
|
||||||
activeSiteHandler()->setActiveFrameInDoc(m_lastSelectedDoc, frame);
|
activeSiteHandler()->setActiveFrameInDoc(m_lastSelectedDoc, frame);
|
||||||
|
|
||||||
|
notifyActiveSiteChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Context::onSetRange(const DocRange& range)
|
void Context::onSetRange(const DocRange& range)
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// Aseprite
|
// Aseprite
|
||||||
// Copyright (C) 2018-2020 Igara Studio S.A.
|
// Copyright (C) 2018-2021 Igara Studio S.A.
|
||||||
// Copyright (C) 2001-2018 David Capello
|
// Copyright (C) 2001-2018 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
@ -110,7 +110,7 @@ namespace app {
|
|||||||
void onRemoveDocument(Doc* doc) override;
|
void onRemoveDocument(Doc* doc) override;
|
||||||
|
|
||||||
virtual void onGetActiveSite(Site* site) const;
|
virtual void onGetActiveSite(Site* site) const;
|
||||||
virtual void onSetActiveDocument(Doc* doc);
|
virtual void onSetActiveDocument(Doc* doc, bool notify);
|
||||||
virtual void onSetActiveLayer(doc::Layer* layer);
|
virtual void onSetActiveLayer(doc::Layer* layer);
|
||||||
virtual void onSetActiveFrame(const doc::frame_t frame);
|
virtual void onSetActiveFrame(const doc::frame_t frame);
|
||||||
virtual void onSetRange(const DocRange& range);
|
virtual void onSetRange(const DocRange& range);
|
||||||
@ -122,10 +122,12 @@ namespace app {
|
|||||||
private:
|
private:
|
||||||
ActiveSiteHandler* activeSiteHandler() const;
|
ActiveSiteHandler* activeSiteHandler() const;
|
||||||
|
|
||||||
|
// This must be defined before m_docs because ActiveSiteHandler
|
||||||
|
// will be an observer of all documents.
|
||||||
|
mutable std::unique_ptr<ActiveSiteHandler> m_activeSiteHandler;
|
||||||
mutable Docs m_docs;
|
mutable Docs m_docs;
|
||||||
ContextFlags m_flags; // Last updated flags.
|
ContextFlags m_flags; // Last updated flags.
|
||||||
Doc* m_lastSelectedDoc;
|
Doc* m_lastSelectedDoc;
|
||||||
mutable std::unique_ptr<ActiveSiteHandler> m_activeSiteHandler;
|
|
||||||
mutable std::unique_ptr<Preferences> m_preferences;
|
mutable std::unique_ptr<Preferences> m_preferences;
|
||||||
|
|
||||||
DISABLE_COPYING(Context);
|
DISABLE_COPYING(Context);
|
||||||
|
@ -70,6 +70,14 @@ Doc::Doc(Sprite* sprite)
|
|||||||
Doc::~Doc()
|
Doc::~Doc()
|
||||||
{
|
{
|
||||||
DOC_TRACE("DOC: Deleting", this);
|
DOC_TRACE("DOC: Deleting", this);
|
||||||
|
|
||||||
|
try {
|
||||||
|
notify_observers<Doc*>(&DocObserver::onDestroy, this);
|
||||||
|
}
|
||||||
|
catch (...) {
|
||||||
|
LOG(ERROR, "DOC: Exception on DocObserver::onDestroy()\n");
|
||||||
|
}
|
||||||
|
|
||||||
removeFromContext();
|
removeFromContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ namespace app {
|
|||||||
public:
|
public:
|
||||||
virtual ~DocObserver() { }
|
virtual ~DocObserver() { }
|
||||||
|
|
||||||
|
virtual void onDestroy(Doc* doc) { }
|
||||||
virtual void onFileNameChanged(Doc* doc) { }
|
virtual void onFileNameChanged(Doc* doc) { }
|
||||||
|
|
||||||
// General update. If an observer receives this event, it's because
|
// General update. If an observer receives this event, it's because
|
||||||
@ -80,8 +81,6 @@ namespace app {
|
|||||||
// Slices
|
// Slices
|
||||||
virtual void onSliceNameChange(DocEvent& ev) { }
|
virtual void onSliceNameChange(DocEvent& ev) { }
|
||||||
|
|
||||||
// Called to destroy the observable. (Here you could call "delete this".)
|
|
||||||
virtual void dispose() { }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace app
|
} // namespace app
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
// Aseprite
|
// Aseprite
|
||||||
|
// Copyright (C) 2021 Igara Studio S.A.
|
||||||
// Copyright (C) 2015-2018 David Capello
|
// Copyright (C) 2015-2018 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
@ -19,12 +20,12 @@ namespace app {
|
|||||||
class DocUndoObserver {
|
class DocUndoObserver {
|
||||||
public:
|
public:
|
||||||
virtual ~DocUndoObserver() { }
|
virtual ~DocUndoObserver() { }
|
||||||
virtual void onAddUndoState(DocUndo* history) = 0;
|
virtual void onAddUndoState(DocUndo* history) { }
|
||||||
virtual void onDeleteUndoState(DocUndo* history,
|
virtual void onDeleteUndoState(DocUndo* history,
|
||||||
undo::UndoState* state) = 0;
|
undo::UndoState* state) { }
|
||||||
virtual void onCurrentUndoStateChange(DocUndo* history) = 0;
|
virtual void onCurrentUndoStateChange(DocUndo* history) { }
|
||||||
virtual void onClearRedo(DocUndo* history) = 0;
|
virtual void onClearRedo(DocUndo* history) { }
|
||||||
virtual void onTotalUndoSizeChange(DocUndo* history) = 0;
|
virtual void onTotalUndoSizeChange(DocUndo* history) { }
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace app
|
} // namespace app
|
||||||
|
@ -126,7 +126,7 @@ int AppFS_makeDirectory(lua_State* L)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ask_access(L, path, FileAccessMode::Write, true))
|
if (!ask_access(L, path, FileAccessMode::Full, ResourceType::File))
|
||||||
return luaL_error(L, "the script doesn't have access to create the directory '%s'", path);
|
return luaL_error(L, "the script doesn't have access to create the directory '%s'", path);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -148,7 +148,7 @@ int AppFS_makeAllDirectories(lua_State* L)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ask_access(L, path, FileAccessMode::Write, true))
|
if (!ask_access(L, path, FileAccessMode::Write, ResourceType::File))
|
||||||
return luaL_error(L, "the script doesn't have access to create all directories '%s'", path);
|
return luaL_error(L, "the script doesn't have access to create all directories '%s'", path);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -170,7 +170,7 @@ int AppFS_removeDirectory(lua_State* L)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ask_access(L, path, FileAccessMode::Write, true))
|
if (!ask_access(L, path, FileAccessMode::Write, ResourceType::File))
|
||||||
return luaL_error(L, "the script doesn't have access to remove the directory '%s'", path);
|
return luaL_error(L, "the script doesn't have access to remove the directory '%s'", path);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -58,7 +58,7 @@ int load_sprite_from_file(lua_State* L, const char* filename,
|
|||||||
const LoadSpriteFromFileParam param)
|
const LoadSpriteFromFileParam param)
|
||||||
{
|
{
|
||||||
std::string absFn = base::get_absolute_path(filename);
|
std::string absFn = base::get_absolute_path(filename);
|
||||||
if (!ask_access(L, absFn.c_str(), FileAccessMode::Read, true))
|
if (!ask_access(L, absFn.c_str(), FileAccessMode::Read, ResourceType::File))
|
||||||
return luaL_error(L, "script doesn't have access to open file %s",
|
return luaL_error(L, "script doesn't have access to open file %s",
|
||||||
absFn.c_str());
|
absFn.c_str());
|
||||||
|
|
||||||
@ -456,6 +456,12 @@ int App_useTool(lua_State* L)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int App_get_events(lua_State* L)
|
||||||
|
{
|
||||||
|
push_app_events(L);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
int App_get_activeSprite(lua_State* L)
|
int App_get_activeSprite(lua_State* L)
|
||||||
{
|
{
|
||||||
app::Context* ctx = App::instance()->context();
|
app::Context* ctx = App::instance()->context();
|
||||||
@ -736,6 +742,7 @@ const Property App_properties[] = {
|
|||||||
{ "range", App_get_range, nullptr },
|
{ "range", App_get_range, nullptr },
|
||||||
{ "isUIAvailable", App_get_isUIAvailable, nullptr },
|
{ "isUIAvailable", App_get_isUIAvailable, nullptr },
|
||||||
{ "defaultPalette", App_get_defaultPalette, App_set_defaultPalette },
|
{ "defaultPalette", App_get_defaultPalette, App_set_defaultPalette },
|
||||||
|
{ "events", App_get_events, nullptr },
|
||||||
{ nullptr, nullptr, nullptr }
|
{ nullptr, nullptr, nullptr }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -155,6 +155,7 @@ void register_color_space_class(lua_State* L);
|
|||||||
#ifdef ENABLE_UI
|
#ifdef ENABLE_UI
|
||||||
void register_dialog_class(lua_State* L);
|
void register_dialog_class(lua_State* L);
|
||||||
#endif
|
#endif
|
||||||
|
void register_events_class(lua_State* L);
|
||||||
void register_frame_class(lua_State* L);
|
void register_frame_class(lua_State* L);
|
||||||
void register_frames_class(lua_State* L);
|
void register_frames_class(lua_State* L);
|
||||||
void register_image_class(lua_State* L);
|
void register_image_class(lua_State* L);
|
||||||
@ -180,6 +181,7 @@ void register_tag_class(lua_State* L);
|
|||||||
void register_tags_class(lua_State* L);
|
void register_tags_class(lua_State* L);
|
||||||
void register_tool_class(lua_State* L);
|
void register_tool_class(lua_State* L);
|
||||||
void register_version_class(lua_State* L);
|
void register_version_class(lua_State* L);
|
||||||
|
void register_websocket_class(lua_State* L);
|
||||||
|
|
||||||
void set_app_params(lua_State* L, const Params& params);
|
void set_app_params(lua_State* L, const Params& params);
|
||||||
|
|
||||||
@ -386,6 +388,7 @@ Engine::Engine()
|
|||||||
#ifdef ENABLE_UI
|
#ifdef ENABLE_UI
|
||||||
register_dialog_class(L);
|
register_dialog_class(L);
|
||||||
#endif
|
#endif
|
||||||
|
register_events_class(L);
|
||||||
register_frame_class(L);
|
register_frame_class(L);
|
||||||
register_frames_class(L);
|
register_frames_class(L);
|
||||||
register_image_class(L);
|
register_image_class(L);
|
||||||
@ -411,6 +414,9 @@ Engine::Engine()
|
|||||||
register_tags_class(L);
|
register_tags_class(L);
|
||||||
register_tool_class(L);
|
register_tool_class(L);
|
||||||
register_version_class(L);
|
register_version_class(L);
|
||||||
|
#if ENABLE_WEBSOCKET
|
||||||
|
register_websocket_class(L);
|
||||||
|
#endif
|
||||||
|
|
||||||
// Check that we have a clean start (without dirty in the stack)
|
// Check that we have a clean start (without dirty in the stack)
|
||||||
ASSERT(lua_gettop(L) == top);
|
ASSERT(lua_gettop(L) == top);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// Aseprite
|
// Aseprite
|
||||||
// Copyright (C) 2018-2020 Igara Studio S.A.
|
// Copyright (C) 2018-2021 Igara Studio S.A.
|
||||||
// Copyright (C) 2001-2018 David Capello
|
// Copyright (C) 2001-2018 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
@ -58,13 +58,6 @@ namespace app {
|
|||||||
|
|
||||||
namespace script {
|
namespace script {
|
||||||
|
|
||||||
enum class FileAccessMode {
|
|
||||||
Execute = 1,
|
|
||||||
Write = 2,
|
|
||||||
Read = 4,
|
|
||||||
Full = 7
|
|
||||||
};
|
|
||||||
|
|
||||||
class EngineDelegate {
|
class EngineDelegate {
|
||||||
public:
|
public:
|
||||||
virtual ~EngineDelegate() { }
|
virtual ~EngineDelegate() { }
|
||||||
@ -121,6 +114,7 @@ namespace app {
|
|||||||
EngineDelegate* m_oldDelegate;
|
EngineDelegate* m_oldDelegate;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void push_app_events(lua_State* L);
|
||||||
int push_image_iterator_function(lua_State* L, const doc::Image* image, int extraArgIndex);
|
int push_image_iterator_function(lua_State* L, const doc::Image* image, int extraArgIndex);
|
||||||
void push_brush(lua_State* L, const doc::BrushRef& brush);
|
void push_brush(lua_State* L, const doc::BrushRef& brush);
|
||||||
void push_cel_image(lua_State* L, doc::Cel* cel);
|
void push_cel_image(lua_State* L, doc::Cel* cel);
|
||||||
@ -136,6 +130,7 @@ namespace app {
|
|||||||
void push_palette(lua_State* L, doc::Palette* palette);
|
void push_palette(lua_State* L, doc::Palette* palette);
|
||||||
void push_plugin(lua_State* L, Extension* ext);
|
void push_plugin(lua_State* L, Extension* ext);
|
||||||
void push_sprite_cel(lua_State* L, doc::Cel* cel);
|
void push_sprite_cel(lua_State* L, doc::Cel* cel);
|
||||||
|
void push_sprite_events(lua_State* L, doc::Sprite* sprite);
|
||||||
void push_sprite_frame(lua_State* L, doc::Sprite* sprite, doc::frame_t frame);
|
void push_sprite_frame(lua_State* L, doc::Sprite* sprite, doc::frame_t frame);
|
||||||
void push_sprite_frames(lua_State* L, doc::Sprite* sprite);
|
void push_sprite_frames(lua_State* L, doc::Sprite* sprite);
|
||||||
void push_sprite_frames(lua_State* L, doc::Sprite* sprite, const std::vector<doc::frame_t>& frames);
|
void push_sprite_frames(lua_State* L, doc::Sprite* sprite, const std::vector<doc::frame_t>& frames);
|
||||||
|
343
src/app/script/events_class.cpp
Normal file
343
src/app/script/events_class.cpp
Normal file
@ -0,0 +1,343 @@
|
|||||||
|
// Aseprite
|
||||||
|
// Copyright (C) 2021 Igara Studio S.A.
|
||||||
|
//
|
||||||
|
// This program is distributed under the terms of
|
||||||
|
// the End-User License Agreement for Aseprite.
|
||||||
|
|
||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include "config.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "app/app.h"
|
||||||
|
#include "app/context.h"
|
||||||
|
#include "app/context_observer.h"
|
||||||
|
#include "app/doc.h"
|
||||||
|
#include "app/doc_undo.h"
|
||||||
|
#include "app/doc_undo_observer.h"
|
||||||
|
#include "app/script/docobj.h"
|
||||||
|
#include "app/script/engine.h"
|
||||||
|
#include "app/script/luacpp.h"
|
||||||
|
#include "doc/document.h"
|
||||||
|
#include "doc/sprite.h"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace app {
|
||||||
|
namespace script {
|
||||||
|
|
||||||
|
using namespace doc;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
using EventListener = int;
|
||||||
|
|
||||||
|
class AppEvents;
|
||||||
|
class SpriteEvents;
|
||||||
|
static std::unique_ptr<AppEvents> g_appEvents;
|
||||||
|
static std::map<doc::ObjectId, std::unique_ptr<SpriteEvents>> g_spriteEvents;
|
||||||
|
|
||||||
|
class Events {
|
||||||
|
public:
|
||||||
|
using EventType = int;
|
||||||
|
|
||||||
|
Events() { }
|
||||||
|
virtual ~Events() { }
|
||||||
|
Events(const Events&) = delete;
|
||||||
|
Events& operator=(const Events&) = delete;
|
||||||
|
|
||||||
|
virtual EventType eventType(const char* eventName) const = 0;
|
||||||
|
|
||||||
|
bool hasListener(EventListener callbackRef) const {
|
||||||
|
for (auto& listeners : m_listeners) {
|
||||||
|
for (EventListener listener : listeners) {
|
||||||
|
if (listener == callbackRef)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void add(EventType eventType, EventListener callbackRef) {
|
||||||
|
if (eventType >= m_listeners.size())
|
||||||
|
m_listeners.resize(eventType+1);
|
||||||
|
|
||||||
|
auto& listeners = m_listeners[eventType];
|
||||||
|
listeners.push_back(callbackRef);
|
||||||
|
if (listeners.size() == 1)
|
||||||
|
onAddFirstListener(eventType);
|
||||||
|
}
|
||||||
|
|
||||||
|
void remove(EventListener callbackRef) {
|
||||||
|
for (int i=0; i<int(m_listeners.size()); ++i) {
|
||||||
|
EventListeners& listeners = m_listeners[i];
|
||||||
|
auto it = listeners.begin();
|
||||||
|
auto end = listeners.end();
|
||||||
|
bool removed = false;
|
||||||
|
for (; it != end; ) {
|
||||||
|
if (*it == callbackRef) {
|
||||||
|
removed = true;
|
||||||
|
it = listeners.erase(it);
|
||||||
|
end = listeners.end();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
if (removed && listeners.empty())
|
||||||
|
onRemoveLastListener(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void call(EventType eventType) {
|
||||||
|
if (eventType >= m_listeners.size())
|
||||||
|
return;
|
||||||
|
|
||||||
|
script::Engine* engine = App::instance()->scriptEngine();
|
||||||
|
lua_State* L = engine->luaState();
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (EventListener callbackRef : m_listeners[eventType]) {
|
||||||
|
lua_rawgeti(L, LUA_REGISTRYINDEX, callbackRef);
|
||||||
|
if (lua_pcall(L, 0, 0, 0)) {
|
||||||
|
if (const char* s = lua_tostring(L, -1))
|
||||||
|
engine->consolePrint(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (const std::exception& ex) {
|
||||||
|
engine->consolePrint(ex.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
virtual void onAddFirstListener(EventType eventType) = 0;
|
||||||
|
virtual void onRemoveLastListener(EventType eventType) = 0;
|
||||||
|
|
||||||
|
using EventListeners = std::vector<EventListener>;
|
||||||
|
std::vector<EventListeners> m_listeners;
|
||||||
|
};
|
||||||
|
|
||||||
|
class AppEvents : public Events
|
||||||
|
, private ContextObserver {
|
||||||
|
public:
|
||||||
|
enum : EventType { Unknown = -1, SiteChange };
|
||||||
|
|
||||||
|
AppEvents() {
|
||||||
|
}
|
||||||
|
|
||||||
|
EventType eventType(const char* eventName) const {
|
||||||
|
if (std::strcmp(eventName, "sitechange") == 0)
|
||||||
|
return SiteChange;
|
||||||
|
else
|
||||||
|
return Unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
void onAddFirstListener(EventType eventType) override {
|
||||||
|
switch (eventType) {
|
||||||
|
case SiteChange: {
|
||||||
|
App::instance()->context()->add_observer(this);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onRemoveLastListener(EventType eventType) override {
|
||||||
|
switch (eventType) {
|
||||||
|
case SiteChange: {
|
||||||
|
App::instance()->context()->remove_observer(this);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContextObserver impl
|
||||||
|
void onActiveSiteChange(const Site& site) override { call(SiteChange); }
|
||||||
|
};
|
||||||
|
|
||||||
|
class SpriteEvents : public Events
|
||||||
|
, public DocUndoObserver
|
||||||
|
, public DocObserver {
|
||||||
|
public:
|
||||||
|
enum : EventType { Unknown = -1, Change };
|
||||||
|
|
||||||
|
SpriteEvents(const Sprite* sprite)
|
||||||
|
: m_spriteId(sprite->id()) {
|
||||||
|
doc()->add_observer(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
~SpriteEvents() {
|
||||||
|
if (m_observingUndo) {
|
||||||
|
doc()->undoHistory()->remove_observer(this);
|
||||||
|
m_observingUndo = false;
|
||||||
|
}
|
||||||
|
doc()->remove_observer(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
EventType eventType(const char* eventName) const {
|
||||||
|
if (std::strcmp(eventName, "change") == 0)
|
||||||
|
return Change;
|
||||||
|
else
|
||||||
|
return Unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
// DocObserver impl
|
||||||
|
void onDestroy(Doc* doc) override {
|
||||||
|
auto it = g_spriteEvents.find(m_spriteId);
|
||||||
|
ASSERT(it != g_spriteEvents.end());
|
||||||
|
if (it != g_spriteEvents.end())
|
||||||
|
g_spriteEvents.erase(it);
|
||||||
|
}
|
||||||
|
|
||||||
|
// DocUndoObserver impl
|
||||||
|
void onAddUndoState(DocUndo* history) override { call(Change); }
|
||||||
|
void onCurrentUndoStateChange(DocUndo* history) override { call(Change); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
void onAddFirstListener(EventType eventType) override {
|
||||||
|
switch (eventType) {
|
||||||
|
case Change:
|
||||||
|
ASSERT(!m_observingUndo);
|
||||||
|
doc()->undoHistory()->add_observer(this);
|
||||||
|
m_observingUndo = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onRemoveLastListener(EventType eventType) override {
|
||||||
|
switch (eventType) {
|
||||||
|
case Change: {
|
||||||
|
if (m_observingUndo) {
|
||||||
|
doc()->undoHistory()->remove_observer(this);
|
||||||
|
m_observingUndo = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Doc* doc() {
|
||||||
|
Sprite* sprite = doc::get<Sprite>(m_spriteId);
|
||||||
|
if (sprite)
|
||||||
|
return static_cast<Doc*>(sprite->document());
|
||||||
|
else
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjectId m_spriteId;
|
||||||
|
bool m_observingUndo = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
int Events_on(lua_State* L)
|
||||||
|
{
|
||||||
|
auto evs = get_ptr<Events>(L, 1);
|
||||||
|
const char* eventName = lua_tostring(L, 2);
|
||||||
|
if (!eventName)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
const int type = evs->eventType(eventName);
|
||||||
|
if (type < 0)
|
||||||
|
return luaL_error(L, "invalid event name to listen");
|
||||||
|
|
||||||
|
if (!lua_isfunction(L, 3))
|
||||||
|
return luaL_error(L, "second argument must be a function");
|
||||||
|
|
||||||
|
// Copy the callback function to add it to the global registry
|
||||||
|
lua_pushvalue(L, 3);
|
||||||
|
int callbackRef = luaL_ref(L, LUA_REGISTRYINDEX);
|
||||||
|
evs->add(type, callbackRef);
|
||||||
|
|
||||||
|
// Return the callback ref (this is an EventListener easier to use
|
||||||
|
// in Events_off())
|
||||||
|
lua_pushinteger(L, callbackRef);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Events_off(lua_State* L)
|
||||||
|
{
|
||||||
|
auto evs = get_ptr<Events>(L, 1);
|
||||||
|
int callbackRef = LUA_REFNIL;
|
||||||
|
|
||||||
|
// Remove by listener value
|
||||||
|
if (lua_isinteger(L, 2)) {
|
||||||
|
callbackRef = lua_tointeger(L, 2);
|
||||||
|
}
|
||||||
|
// Remove by function reference
|
||||||
|
else if (lua_isfunction(L, 2)) {
|
||||||
|
lua_pushnil(L);
|
||||||
|
while (lua_next(L, LUA_REGISTRYINDEX) != 0) {
|
||||||
|
if (lua_isnumber(L, -2) &&
|
||||||
|
lua_isfunction(L, -1)) {
|
||||||
|
int i = lua_tointeger(L, -2);
|
||||||
|
if (// Compare value=function in 2nd argument
|
||||||
|
lua_compare(L, -1, 2, LUA_OPEQ) &&
|
||||||
|
// Check that this Events contain this reference
|
||||||
|
evs->hasListener(i)) {
|
||||||
|
callbackRef = i;
|
||||||
|
lua_pop(L, 2); // Pop value and key
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lua_pop(L, 1); // Pop the value, leave the key for next lua_next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return luaL_error(L, "first argument must be a function or a EventListener");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (callbackRef != LUA_REFNIL) {
|
||||||
|
evs->remove(callbackRef);
|
||||||
|
luaL_unref(L, LUA_REGISTRYINDEX, callbackRef);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We don't need a __gc (to call ~Events()), because Events instances
|
||||||
|
// will be deleted when the Sprite is deleted or on App Exit
|
||||||
|
const luaL_Reg Events_methods[] = {
|
||||||
|
{ "on", Events_on },
|
||||||
|
{ "off", Events_off },
|
||||||
|
{ nullptr, nullptr }
|
||||||
|
};
|
||||||
|
|
||||||
|
} // anonymous namespace
|
||||||
|
|
||||||
|
DEF_MTNAME(Events);
|
||||||
|
|
||||||
|
void register_events_class(lua_State* L)
|
||||||
|
{
|
||||||
|
REG_CLASS(L, Events);
|
||||||
|
}
|
||||||
|
|
||||||
|
void push_app_events(lua_State* L)
|
||||||
|
{
|
||||||
|
if (!g_appEvents) {
|
||||||
|
App::instance()->Exit.connect([]{ g_appEvents.reset(); });
|
||||||
|
g_appEvents.reset(new AppEvents);
|
||||||
|
}
|
||||||
|
push_ptr<Events>(L, g_appEvents.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
void push_sprite_events(lua_State* L, Sprite* sprite)
|
||||||
|
{
|
||||||
|
ASSERT(sprite);
|
||||||
|
|
||||||
|
SpriteEvents* spriteEvents;
|
||||||
|
|
||||||
|
auto it = g_spriteEvents.find(sprite->id());
|
||||||
|
if (it != g_spriteEvents.end())
|
||||||
|
spriteEvents = it->second.get();
|
||||||
|
else {
|
||||||
|
spriteEvents = new SpriteEvents(sprite);
|
||||||
|
g_spriteEvents[sprite->id()].reset(spriteEvents);
|
||||||
|
}
|
||||||
|
|
||||||
|
push_ptr<Events>(L, spriteEvents);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace script
|
||||||
|
} // namespace app
|
@ -1,5 +1,5 @@
|
|||||||
// Aseprite
|
// Aseprite
|
||||||
// Copyright (C) 2018-2020 Igara Studio S.A.
|
// Copyright (C) 2018-2021 Igara Studio S.A.
|
||||||
// Copyright (C) 2015-2018 David Capello
|
// Copyright (C) 2015-2018 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
@ -33,6 +33,7 @@
|
|||||||
#include "render/render.h"
|
#include "render/render.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <cstring>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
namespace app {
|
namespace app {
|
||||||
@ -366,7 +367,7 @@ int Image_saveAs(lua_State* L)
|
|||||||
return luaL_error(L, "missing filename in Image:saveAs()");
|
return luaL_error(L, "missing filename in Image:saveAs()");
|
||||||
|
|
||||||
std::string absFn = base::get_absolute_path(fn);
|
std::string absFn = base::get_absolute_path(fn);
|
||||||
if (!ask_access(L, absFn.c_str(), FileAccessMode::Write, true))
|
if (!ask_access(L, absFn.c_str(), FileAccessMode::Write, ResourceType::File))
|
||||||
return luaL_error(L, "script doesn't have access to write file %s",
|
return luaL_error(L, "script doesn't have access to write file %s",
|
||||||
absFn.c_str());
|
absFn.c_str());
|
||||||
|
|
||||||
@ -483,6 +484,37 @@ int Image_resize(lua_State* L)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int Image_get_rowStride(lua_State* L)
|
||||||
|
{
|
||||||
|
const auto obj = get_obj<ImageObj>(L, 1);
|
||||||
|
lua_pushinteger(L, obj->image(L)->getRowStrideSize());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Image_get_bytes(lua_State* L)
|
||||||
|
{
|
||||||
|
const auto img = get_obj<ImageObj>(L, 1)->image(L);
|
||||||
|
lua_pushlstring(L, (const char*)img->getPixelAddress(0, 0), img->getRowStrideSize() * img->height());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Image_set_bytes(lua_State* L)
|
||||||
|
{
|
||||||
|
const auto img = get_obj<ImageObj>(L, 1)->image(L);
|
||||||
|
size_t bytes_size, bytes_needed = img->getRowStrideSize() * img->height();
|
||||||
|
const char* bytes = lua_tolstring(L, 2, &bytes_size);
|
||||||
|
|
||||||
|
if (bytes_size == bytes_needed) {
|
||||||
|
std::memcpy(img->getPixelAddress(0, 0), bytes, bytes_size);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
lua_pushfstring(L, "Data size does not match: given %d, needed %d.", bytes_size, bytes_needed);
|
||||||
|
lua_error(L);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
int Image_get_width(lua_State* L)
|
int Image_get_width(lua_State* L)
|
||||||
{
|
{
|
||||||
const auto obj = get_obj<ImageObj>(L, 1);
|
const auto obj = get_obj<ImageObj>(L, 1);
|
||||||
@ -537,6 +569,8 @@ const luaL_Reg Image_methods[] = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const Property Image_properties[] = {
|
const Property Image_properties[] = {
|
||||||
|
{ "rowStride", Image_get_rowStride, nullptr },
|
||||||
|
{ "bytes", Image_get_bytes, Image_set_bytes },
|
||||||
{ "width", Image_get_width, nullptr },
|
{ "width", Image_get_width, nullptr },
|
||||||
{ "height", Image_get_height, nullptr },
|
{ "height", Image_get_height, nullptr },
|
||||||
{ "colorMode", Image_get_colorMode, nullptr },
|
{ "colorMode", Image_get_colorMode, nullptr },
|
||||||
|
@ -76,7 +76,7 @@ int Palette_new(lua_State* L)
|
|||||||
std::string absFn = base::get_absolute_path(fromFile);
|
std::string absFn = base::get_absolute_path(fromFile);
|
||||||
lua_pop(L, 1);
|
lua_pop(L, 1);
|
||||||
|
|
||||||
if (!ask_access(L, absFn.c_str(), FileAccessMode::Read, true))
|
if (!ask_access(L, absFn.c_str(), FileAccessMode::Read, ResourceType::File))
|
||||||
return luaL_error(L, "script doesn't have access to open file %s",
|
return luaL_error(L, "script doesn't have access to open file %s",
|
||||||
absFn.c_str());
|
absFn.c_str());
|
||||||
|
|
||||||
@ -105,7 +105,7 @@ int Palette_new(lua_State* L)
|
|||||||
if (!idAndPaths[id].empty()) {
|
if (!idAndPaths[id].empty()) {
|
||||||
std::string absFn = base::get_absolute_path(idAndPaths[id]);
|
std::string absFn = base::get_absolute_path(idAndPaths[id]);
|
||||||
|
|
||||||
if (!ask_access(L, absFn.c_str(), FileAccessMode::Read, true))
|
if (!ask_access(L, absFn.c_str(), FileAccessMode::Read, ResourceType::File))
|
||||||
return luaL_error(L, "script doesn't have access to open file %s",
|
return luaL_error(L, "script doesn't have access to open file %s",
|
||||||
absFn.c_str());
|
absFn.c_str());
|
||||||
|
|
||||||
@ -240,7 +240,7 @@ int Palette_saveAs(lua_State* L)
|
|||||||
const char* fn = luaL_checkstring(L, 2);
|
const char* fn = luaL_checkstring(L, 2);
|
||||||
if (fn) {
|
if (fn) {
|
||||||
std::string absFn = base::get_absolute_path(fn);
|
std::string absFn = base::get_absolute_path(fn);
|
||||||
if (!ask_access(L, absFn.c_str(), FileAccessMode::Write, true))
|
if (!ask_access(L, absFn.c_str(), FileAccessMode::Write, ResourceType::File))
|
||||||
return luaL_error(L, "script doesn't have access to write file %s",
|
return luaL_error(L, "script doesn't have access to write file %s",
|
||||||
absFn.c_str());
|
absFn.c_str());
|
||||||
save_palette(absFn.c_str(), pal, pal->size());
|
save_palette(absFn.c_str(), pal, pal->size());
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// Aseprite
|
// Aseprite
|
||||||
// Copyright (C) 2019 Igara Studio S.A.
|
// Copyright (C) 2019-2021 Igara Studio S.A.
|
||||||
// Copyright (C) 2018 David Capello
|
// Copyright (C) 2018 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
@ -81,7 +81,7 @@ int secure_io_open(lua_State* L)
|
|||||||
mode = FileAccessMode::Write;
|
mode = FileAccessMode::Write;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ask_access(L, absFilename.c_str(), mode, true)) {
|
if (!ask_access(L, absFilename.c_str(), mode, ResourceType::File)) {
|
||||||
return luaL_error(L, "the script doesn't have access to file '%s'",
|
return luaL_error(L, "the script doesn't have access to file '%s'",
|
||||||
absFilename.c_str());
|
absFilename.c_str());
|
||||||
}
|
}
|
||||||
@ -101,7 +101,7 @@ int secure_os_execute(lua_State* L)
|
|||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
const char* cmd = lua_tostring(L, 1);
|
const char* cmd = lua_tostring(L, 1);
|
||||||
if (!ask_access(L, cmd, FileAccessMode::Execute, false)) {
|
if (!ask_access(L, cmd, FileAccessMode::Execute, ResourceType::Command)) {
|
||||||
// Stop script
|
// Stop script
|
||||||
return luaL_error(L, "the script doesn't have access to execute the command: '%s'",
|
return luaL_error(L, "the script doesn't have access to execute the command: '%s'",
|
||||||
cmd);
|
cmd);
|
||||||
@ -117,7 +117,7 @@ int secure_os_execute(lua_State* L)
|
|||||||
bool ask_access(lua_State* L,
|
bool ask_access(lua_State* L,
|
||||||
const char* filename,
|
const char* filename,
|
||||||
const FileAccessMode mode,
|
const FileAccessMode mode,
|
||||||
const bool canOpenFile)
|
const ResourceType resourceType)
|
||||||
{
|
{
|
||||||
#ifdef ENABLE_UI
|
#ifdef ENABLE_UI
|
||||||
// Ask for permission to open the file
|
// Ask for permission to open the file
|
||||||
@ -136,6 +136,8 @@ bool ask_access(lua_State* L,
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
std::string allowButtonText =
|
std::string allowButtonText =
|
||||||
|
mode == FileAccessMode::OpenSocket ?
|
||||||
|
Strings::script_access_allow_open_conn_access():
|
||||||
mode == FileAccessMode::Execute ?
|
mode == FileAccessMode::Execute ?
|
||||||
Strings::script_access_allow_execute_access():
|
Strings::script_access_allow_execute_access():
|
||||||
mode == FileAccessMode::Write ?
|
mode == FileAccessMode::Write ?
|
||||||
@ -144,10 +146,17 @@ bool ask_access(lua_State* L,
|
|||||||
|
|
||||||
app::gen::ScriptAccess dlg;
|
app::gen::ScriptAccess dlg;
|
||||||
dlg.script()->setText(script);
|
dlg.script()->setText(script);
|
||||||
dlg.fileLabel()->setText(
|
|
||||||
canOpenFile ?
|
{
|
||||||
Strings::script_access_file_label():
|
std::string label;
|
||||||
Strings::script_access_command_label());
|
switch (resourceType) {
|
||||||
|
case ResourceType::File: label = Strings::script_access_file_label(); break;
|
||||||
|
case ResourceType::Command: label = Strings::script_access_command_label(); break;
|
||||||
|
case ResourceType::WebSocket: label = Strings::script_access_websocket_label(); break;
|
||||||
|
}
|
||||||
|
dlg.fileLabel()->setText(label);
|
||||||
|
}
|
||||||
|
|
||||||
dlg.file()->setText(filename);
|
dlg.file()->setText(filename);
|
||||||
dlg.allow()->setText(allowButtonText);
|
dlg.allow()->setText(allowButtonText);
|
||||||
dlg.allow()->processMnemonicFromText();
|
dlg.allow()->processMnemonicFromText();
|
||||||
@ -174,7 +183,7 @@ bool ask_access(lua_State* L,
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (canOpenFile) {
|
if (resourceType == ResourceType::File) {
|
||||||
dlg.file()->Click.connect(
|
dlg.file()->Click.connect(
|
||||||
[&dlg]{
|
[&dlg]{
|
||||||
std::string fn = dlg.file()->text();
|
std::string fn = dlg.file()->text();
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
// Aseprite
|
// Aseprite
|
||||||
|
// Copyright (C) 2021 Igara Studio S.A.
|
||||||
// Copyright (C) 2018 David Capello
|
// Copyright (C) 2018 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
@ -17,13 +18,27 @@
|
|||||||
namespace app {
|
namespace app {
|
||||||
namespace script {
|
namespace script {
|
||||||
|
|
||||||
|
enum class FileAccessMode {
|
||||||
|
Execute = 1,
|
||||||
|
Write = 2,
|
||||||
|
Read = 4,
|
||||||
|
Full = 7,
|
||||||
|
OpenSocket = 8,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class ResourceType {
|
||||||
|
File,
|
||||||
|
Command,
|
||||||
|
WebSocket,
|
||||||
|
};
|
||||||
|
|
||||||
int secure_io_open(lua_State* L);
|
int secure_io_open(lua_State* L);
|
||||||
int secure_os_execute(lua_State* L);
|
int secure_os_execute(lua_State* L);
|
||||||
|
|
||||||
bool ask_access(lua_State* L,
|
bool ask_access(lua_State* L,
|
||||||
const char* filename,
|
const char* filename,
|
||||||
const FileAccessMode mode,
|
const FileAccessMode mode,
|
||||||
const bool canOpenFile);
|
const ResourceType resourceType);
|
||||||
|
|
||||||
} // namespace script
|
} // namespace script
|
||||||
} // namespace app
|
} // namespace app
|
||||||
|
@ -9,6 +9,10 @@
|
|||||||
#include "config.h"
|
#include "config.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "app/app.h"
|
||||||
|
#include "app/console.h"
|
||||||
|
#include "app/context.h"
|
||||||
|
#include "app/context_observer.h"
|
||||||
#include "app/script/docobj.h"
|
#include "app/script/docobj.h"
|
||||||
#include "app/script/engine.h"
|
#include "app/script/engine.h"
|
||||||
#include "app/script/luacpp.h"
|
#include "app/script/luacpp.h"
|
||||||
|
@ -27,11 +27,14 @@
|
|||||||
#include "app/color_spaces.h"
|
#include "app/color_spaces.h"
|
||||||
#include "app/commands/commands.h"
|
#include "app/commands/commands.h"
|
||||||
#include "app/commands/params.h"
|
#include "app/commands/params.h"
|
||||||
|
#include "app/console.h"
|
||||||
#include "app/context.h"
|
#include "app/context.h"
|
||||||
#include "app/doc.h"
|
#include "app/doc.h"
|
||||||
#include "app/doc_access.h"
|
#include "app/doc_access.h"
|
||||||
#include "app/doc_api.h"
|
#include "app/doc_api.h"
|
||||||
#include "app/doc_range.h"
|
#include "app/doc_range.h"
|
||||||
|
#include "app/doc_undo.h"
|
||||||
|
#include "app/doc_undo_observer.h"
|
||||||
#include "app/file/palette_file.h"
|
#include "app/file/palette_file.h"
|
||||||
#include "app/script/docobj.h"
|
#include "app/script/docobj.h"
|
||||||
#include "app/script/engine.h"
|
#include "app/script/engine.h"
|
||||||
@ -205,7 +208,7 @@ int Sprite_saveAs_base(lua_State* L, std::string& absFn)
|
|||||||
appCtx->setActiveDocument(doc);
|
appCtx->setActiveDocument(doc);
|
||||||
|
|
||||||
absFn = base::get_absolute_path(fn);
|
absFn = base::get_absolute_path(fn);
|
||||||
if (!ask_access(L, absFn.c_str(), FileAccessMode::Write, true))
|
if (!ask_access(L, absFn.c_str(), FileAccessMode::Write, ResourceType::File))
|
||||||
return luaL_error(L, "script doesn't have access to write file %s",
|
return luaL_error(L, "script doesn't have access to write file %s",
|
||||||
absFn.c_str());
|
absFn.c_str());
|
||||||
|
|
||||||
@ -264,7 +267,7 @@ int Sprite_loadPalette(lua_State* L)
|
|||||||
const char* fn = luaL_checkstring(L, 2);
|
const char* fn = luaL_checkstring(L, 2);
|
||||||
if (fn && sprite) {
|
if (fn && sprite) {
|
||||||
std::string absFn = base::get_absolute_path(fn);
|
std::string absFn = base::get_absolute_path(fn);
|
||||||
if (!ask_access(L, absFn.c_str(), FileAccessMode::Read, true))
|
if (!ask_access(L, absFn.c_str(), FileAccessMode::Read, ResourceType::File))
|
||||||
return luaL_error(L, "script doesn't have access to open file %s",
|
return luaL_error(L, "script doesn't have access to open file %s",
|
||||||
absFn.c_str());
|
absFn.c_str());
|
||||||
|
|
||||||
@ -589,6 +592,13 @@ int Sprite_deleteSlice(lua_State* L)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int Sprite_get_events(lua_State* L)
|
||||||
|
{
|
||||||
|
auto sprite = get_docobj<Sprite>(L, 1);
|
||||||
|
push_sprite_events(L, sprite);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
int Sprite_get_filename(lua_State* L)
|
int Sprite_get_filename(lua_State* L)
|
||||||
{
|
{
|
||||||
auto sprite = get_docobj<Sprite>(L, 1);
|
auto sprite = get_docobj<Sprite>(L, 1);
|
||||||
@ -843,6 +853,7 @@ const Property Sprite_properties[] = {
|
|||||||
{ "bounds", Sprite_get_bounds, nullptr },
|
{ "bounds", Sprite_get_bounds, nullptr },
|
||||||
{ "gridBounds", Sprite_get_gridBounds, Sprite_set_gridBounds },
|
{ "gridBounds", Sprite_get_gridBounds, Sprite_set_gridBounds },
|
||||||
{ "pixelRatio", Sprite_get_pixelRatio, Sprite_set_pixelRatio },
|
{ "pixelRatio", Sprite_get_pixelRatio, Sprite_set_pixelRatio },
|
||||||
|
{ "events", Sprite_get_events, nullptr },
|
||||||
{ nullptr, nullptr, nullptr }
|
{ nullptr, nullptr, nullptr }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
213
src/app/script/websocket_class.cpp
Normal file
213
src/app/script/websocket_class.cpp
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
// Aseprite
|
||||||
|
// Copyright (C) 2021 Igara Studio S.A.
|
||||||
|
//
|
||||||
|
// This program is distributed under the terms of
|
||||||
|
// the End-User License Agreement for Aseprite.
|
||||||
|
|
||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include "config.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "app/app.h"
|
||||||
|
#include "app/console.h"
|
||||||
|
#include "app/script/engine.h"
|
||||||
|
#include "app/script/luacpp.h"
|
||||||
|
#include "app/script/security.h"
|
||||||
|
#include "ui/system.h"
|
||||||
|
|
||||||
|
#include <ixwebsocket/IXNetSystem.h>
|
||||||
|
#include <ixwebsocket/IXWebSocket.h>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
namespace app {
|
||||||
|
namespace script {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// Additional "enum" value to make message callback simpler
|
||||||
|
#define MESSAGE_TYPE_BINARY ((int)ix::WebSocketMessageType::Fragment + 10)
|
||||||
|
|
||||||
|
int WebSocket_new(lua_State* L)
|
||||||
|
{
|
||||||
|
static std::once_flag f;
|
||||||
|
std::call_once(f, &ix::initNetSystem);
|
||||||
|
|
||||||
|
auto ws = new ix::WebSocket();
|
||||||
|
|
||||||
|
push_ptr(L, ws);
|
||||||
|
|
||||||
|
if (lua_istable(L, 1)) {
|
||||||
|
lua_getfield(L, 1, "url");
|
||||||
|
if (const char* s = lua_tostring(L, -1)) {
|
||||||
|
if (!ask_access(L, s, FileAccessMode::OpenSocket, ResourceType::WebSocket))
|
||||||
|
return luaL_error(L, "the script doesn't have access to create a WebSocket for '%s'", s);
|
||||||
|
|
||||||
|
ws->setUrl(s);
|
||||||
|
}
|
||||||
|
lua_pop(L, 1);
|
||||||
|
|
||||||
|
lua_getfield(L, 1, "deflate");
|
||||||
|
if (lua_toboolean(L, -1)) {
|
||||||
|
ws->enablePerMessageDeflate();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ws->disablePerMessageDeflate();
|
||||||
|
}
|
||||||
|
lua_pop(L, 1);
|
||||||
|
|
||||||
|
int type = lua_getfield(L, 1, "onreceive");
|
||||||
|
if (type == LUA_TFUNCTION) {
|
||||||
|
int onreceiveRef = luaL_ref(L, LUA_REGISTRYINDEX);
|
||||||
|
|
||||||
|
ws->setOnMessageCallback(
|
||||||
|
[L, ws, onreceiveRef](const ix::WebSocketMessagePtr& msg) {
|
||||||
|
int msgType =
|
||||||
|
(msg->binary ? MESSAGE_TYPE_BINARY : static_cast<int>(msg->type));
|
||||||
|
std::string msgData = msg->str;
|
||||||
|
|
||||||
|
ui::execute_from_ui_thread([=]() {
|
||||||
|
lua_rawgeti(L, LUA_REGISTRYINDEX, onreceiveRef);
|
||||||
|
lua_pushinteger(L, msgType);
|
||||||
|
lua_pushlstring(L, msgData.c_str(), msgData.length());
|
||||||
|
|
||||||
|
if (lua_pcall(L, 2, 0, 0)) {
|
||||||
|
if (const char* s = lua_tostring(L, -1)) {
|
||||||
|
App::instance()->scriptEngine()->consolePrint(s);
|
||||||
|
ws->stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
lua_pop(L, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int WebSocket_gc(lua_State* L)
|
||||||
|
{
|
||||||
|
auto ws = get_ptr<ix::WebSocket>(L, 1);
|
||||||
|
ws->stop();
|
||||||
|
delete ws;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int WebSocket_sendText(lua_State* L)
|
||||||
|
{
|
||||||
|
auto ws = get_ptr<ix::WebSocket>(L, 1);
|
||||||
|
std::stringstream data;
|
||||||
|
int argc = lua_gettop(L);
|
||||||
|
|
||||||
|
for (int i = 2; i <= argc; i++) {
|
||||||
|
size_t bufLen;
|
||||||
|
const char* buf = lua_tolstring(L, i, &bufLen);
|
||||||
|
data.write(buf, bufLen);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ws->sendText(data.str()).success) {
|
||||||
|
lua_pushstring(L, "Websocket error");
|
||||||
|
lua_error(L);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int WebSocket_sendBinary(lua_State* L)
|
||||||
|
{
|
||||||
|
auto ws = get_ptr<ix::WebSocket>(L, 1);
|
||||||
|
std::stringstream data;
|
||||||
|
int argc = lua_gettop(L);
|
||||||
|
|
||||||
|
for (int i = 2; i <= argc; i++) {
|
||||||
|
size_t bufLen;
|
||||||
|
const char* buf = lua_tolstring(L, i, &bufLen);
|
||||||
|
data.write(buf, bufLen);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ws->sendBinary(data.str()).success) {
|
||||||
|
lua_pushstring(L, "Websocket error");
|
||||||
|
lua_error(L);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int WebSocket_sendPing(lua_State* L)
|
||||||
|
{
|
||||||
|
auto ws = get_ptr<ix::WebSocket>(L, 1);
|
||||||
|
size_t bufLen;
|
||||||
|
const char* buf = lua_tolstring(L, 2, &bufLen);
|
||||||
|
std::string data(buf, bufLen);
|
||||||
|
|
||||||
|
if (!ws->ping(data).success) {
|
||||||
|
lua_pushstring(L, "Websocket error");
|
||||||
|
lua_error(L);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int WebSocket_connect(lua_State* L)
|
||||||
|
{
|
||||||
|
auto ws = get_ptr<ix::WebSocket>(L, 1);
|
||||||
|
ws->start();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int WebSocket_close(lua_State* L)
|
||||||
|
{
|
||||||
|
auto ws = get_ptr<ix::WebSocket>(L, 1);
|
||||||
|
ws->stop();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int WebSocket_get_url(lua_State* L)
|
||||||
|
{
|
||||||
|
auto ws = get_ptr<ix::WebSocket>(L, 1);
|
||||||
|
lua_pushstring(L, ws->getUrl().c_str());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const luaL_Reg WebSocket_methods[] = {
|
||||||
|
{ "__gc", WebSocket_gc },
|
||||||
|
{ "close", WebSocket_close },
|
||||||
|
{ "connect", WebSocket_connect },
|
||||||
|
{ "sendText", WebSocket_sendText },
|
||||||
|
{ "sendBinary", WebSocket_sendBinary },
|
||||||
|
{ "sendPing", WebSocket_sendPing },
|
||||||
|
{ nullptr, nullptr }
|
||||||
|
};
|
||||||
|
|
||||||
|
const Property WebSocket_properties[] = {
|
||||||
|
{ "url", WebSocket_get_url, nullptr },
|
||||||
|
{ nullptr, nullptr, nullptr }
|
||||||
|
};
|
||||||
|
|
||||||
|
} // anonymous namespace
|
||||||
|
|
||||||
|
using WebSocket = ix::WebSocket;
|
||||||
|
DEF_MTNAME(WebSocket);
|
||||||
|
|
||||||
|
void register_websocket_class(lua_State* L)
|
||||||
|
{
|
||||||
|
REG_CLASS(L, WebSocket);
|
||||||
|
REG_CLASS_NEW(L, WebSocket);
|
||||||
|
REG_CLASS_PROPERTIES(L, WebSocket);
|
||||||
|
|
||||||
|
// message type enum
|
||||||
|
lua_newtable(L);
|
||||||
|
lua_pushvalue(L, -1);
|
||||||
|
lua_setglobal(L, "WebSocketMessageType");
|
||||||
|
setfield_integer(L, "Text", (int)ix::WebSocketMessageType::Message);
|
||||||
|
setfield_integer(L, "Binary", MESSAGE_TYPE_BINARY);
|
||||||
|
setfield_integer(L, "Open", (int)ix::WebSocketMessageType::Open);
|
||||||
|
setfield_integer(L, "Close", (int)ix::WebSocketMessageType::Close);
|
||||||
|
setfield_integer(L, "Error", (int)ix::WebSocketMessageType::Error);
|
||||||
|
setfield_integer(L, "Ping", (int)ix::WebSocketMessageType::Ping);
|
||||||
|
setfield_integer(L, "Pong", (int)ix::WebSocketMessageType::Pong);
|
||||||
|
setfield_integer(L, "Fragment", (int)ix::WebSocketMessageType::Fragment);
|
||||||
|
lua_pop(L, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace script
|
||||||
|
} // namespace app
|
@ -134,10 +134,10 @@ void UIContext::setActiveView(DocView* docView)
|
|||||||
notifyActiveSiteChanged();
|
notifyActiveSiteChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void UIContext::onSetActiveDocument(Doc* document)
|
void UIContext::onSetActiveDocument(Doc* document, bool notify)
|
||||||
{
|
{
|
||||||
bool notify = (lastSelectedDoc() != document);
|
notify = (notify && lastSelectedDoc() != document);
|
||||||
app::Context::onSetActiveDocument(document);
|
app::Context::onSetActiveDocument(document, false);
|
||||||
|
|
||||||
DocView* docView = getFirstDocView(document);
|
DocView* docView = getFirstDocView(document);
|
||||||
if (docView) { // The view can be null if we are in --batch mode
|
if (docView) { // The view can be null if we are in --batch mode
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// Aseprite
|
// Aseprite
|
||||||
// Copyright (C) 2019-2020 Igara Studio S.A.
|
// Copyright (C) 2019-2021 Igara Studio S.A.
|
||||||
// Copyright (C) 2001-2018 David Capello
|
// Copyright (C) 2001-2018 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
@ -53,7 +53,7 @@ namespace app {
|
|||||||
void onAddDocument(Doc* doc) override;
|
void onAddDocument(Doc* doc) override;
|
||||||
void onRemoveDocument(Doc* doc) override;
|
void onRemoveDocument(Doc* doc) override;
|
||||||
void onGetActiveSite(Site* site) const override;
|
void onGetActiveSite(Site* site) const override;
|
||||||
void onSetActiveDocument(Doc* doc) override;
|
void onSetActiveDocument(Doc* doc, bool notify) override;
|
||||||
void onSetActiveLayer(doc::Layer* layer) override;
|
void onSetActiveLayer(doc::Layer* layer) override;
|
||||||
void onSetActiveFrame(const doc::frame_t frame) override;
|
void onSetActiveFrame(const doc::frame_t frame) override;
|
||||||
void onSetRange(const DocRange& range) override;
|
void onSetRange(const DocRange& range) override;
|
||||||
|
8
third_party/CMakeLists.txt
vendored
8
third_party/CMakeLists.txt
vendored
@ -160,4 +160,12 @@ if(ENABLE_SCRIPTING)
|
|||||||
target_include_directories(lauxlib PUBLIC lua)
|
target_include_directories(lauxlib PUBLIC lua)
|
||||||
target_include_directories(lualib PUBLIC lua)
|
target_include_directories(lualib PUBLIC lua)
|
||||||
target_link_libraries(lauxlib lua)
|
target_link_libraries(lauxlib lua)
|
||||||
|
|
||||||
|
# ixwebsocket
|
||||||
|
if(ENABLE_WEBSOCKET)
|
||||||
|
set(IXWEBSOCKET_INSTALL OFF CACHE BOOL "Install IXWebSocket")
|
||||||
|
add_subdirectory(IXWebSocket)
|
||||||
|
target_include_directories(ixwebsocket PUBLIC)
|
||||||
|
endif()
|
||||||
|
|
||||||
endif()
|
endif()
|
||||||
|
1
third_party/IXWebSocket
vendored
Submodule
1
third_party/IXWebSocket
vendored
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit 9bbd1f1b30bfa6fb52bf1388ab336ef39d0faead
|
Loading…
x
Reference in New Issue
Block a user