mirror of
https://github.com/OpenSpace/OpenSpace.git
synced 2026-04-30 07:49:31 -05:00
Feature/webgui ops (#723)
* Update node packages, download nodejs in build process, start server from openspace process * Patch CEF cmake automatically * Build webserver automatically * Work on CMake for WebBrowser, Webgui and CefWebGui * Map key modifiers to CEF * Smooth time interpolation in webgui * Automatically focus on search field in filter lists * Move webgui code to external repositories * Use asset system to distribute webgui * Remove webgui from main repository * Add support for right click in webgui and improve timetopic * Resolve cmake policy warning * Add relative time interpolation to lua interface * Sanitize json error message before logging error. Workaround for #736 * Added gui properties to scene graph nodes * Add version topic * Add shortcuttopic * Add ability to disable rendering of cefwebgui * Don't do message loop work if there is no browser. * Set correct path to nodejs on unix * Message loop work in presync * modifications for shortcuts in gui, added names for shortcuts * Set properties via lua scripts * Allow gui grouping for shortcuts * Add gui paths keybindings * Blocking keyboard callbacks when webgui has keyboard focus in an editable field * Allow disabling of WebBrowser and CefWebGui * Make it possible to hide GUI * Get rid of redundant dashboard items if web gui is used * Hide WebGUI on slave nodes * Hide WebGUI on main rendering window if a GUI window exists * Enable WebGUI on default unless it is overwritten in the openspace.cfg * Add guiName for propery owners in socket api
This commit is contained in:
committed by
Alexander Bock
parent
ec67169854
commit
7181de4673
@@ -29,10 +29,12 @@
|
||||
#include <modules/server/include/topics/getpropertytopic.h>
|
||||
#include <modules/server/include/topics/luascripttopic.h>
|
||||
#include <modules/server/include/topics/setpropertytopic.h>
|
||||
#include <modules/server/include/topics/shortcuttopic.h>
|
||||
#include <modules/server/include/topics/subscriptiontopic.h>
|
||||
#include <modules/server/include/topics/timetopic.h>
|
||||
#include <modules/server/include/topics/topic.h>
|
||||
#include <modules/server/include/topics/triggerpropertytopic.h>
|
||||
#include <modules/server/include/topics/versiontopic.h>
|
||||
#include <openspace/engine/configuration.h>
|
||||
#include <openspace/engine/globals.h>
|
||||
#include <ghoul/io/socket/socket.h>
|
||||
@@ -48,10 +50,12 @@ namespace {
|
||||
constexpr const char* MessageKeyPayload = "payload";
|
||||
constexpr const char* MessageKeyTopic = "topic";
|
||||
|
||||
constexpr const char* VersionTopicKey = "version";
|
||||
constexpr const char* AuthenticationTopicKey = "authorize";
|
||||
constexpr const char* GetPropertyTopicKey = "get";
|
||||
constexpr const char* LuaScriptTopicKey = "luascript";
|
||||
constexpr const char* SetPropertyTopicKey = "set";
|
||||
constexpr const char* ShortcutTopicKey = "shortcuts";
|
||||
constexpr const char* SubscriptionTopicKey = "subscribe";
|
||||
constexpr const char* TimeTopicKey = "time";
|
||||
constexpr const char* TriggerPropertyTopicKey = "trigger";
|
||||
@@ -70,10 +74,12 @@ Connection::Connection(std::unique_ptr<ghoul::io::Socket> s, std::string address
|
||||
_topicFactory.registerClass<GetPropertyTopic>(GetPropertyTopicKey);
|
||||
_topicFactory.registerClass<LuaScriptTopic>(LuaScriptTopicKey);
|
||||
_topicFactory.registerClass<SetPropertyTopic>(SetPropertyTopicKey);
|
||||
_topicFactory.registerClass<ShortcutTopic>(ShortcutTopicKey);
|
||||
_topicFactory.registerClass<SubscriptionTopic>(SubscriptionTopicKey);
|
||||
_topicFactory.registerClass<TimeTopic>(TimeTopicKey);
|
||||
_topicFactory.registerClass<TriggerPropertyTopic>(TriggerPropertyTopicKey);
|
||||
_topicFactory.registerClass<BounceTopic>(BounceTopicKey);
|
||||
_topicFactory.registerClass<VersionTopic>(VersionTopicKey);
|
||||
|
||||
// see if the default config for requiring auth (on) is overwritten
|
||||
_requireAuthorization = global::configuration.doesRequireSocketAuthentication;
|
||||
@@ -84,9 +90,8 @@ void Connection::handleMessage(const std::string& message) {
|
||||
nlohmann::json j = nlohmann::json::parse(message.c_str());
|
||||
try {
|
||||
handleJson(j);
|
||||
}
|
||||
catch (...) {
|
||||
LERROR(fmt::format("JSON handling error from: {}", message));
|
||||
} catch (const std::exception& e) {
|
||||
LERROR(fmt::format("JSON handling error from: {}. {}", message, e.what()));
|
||||
}
|
||||
} catch (...) {
|
||||
if (!isAuthorized()) {
|
||||
@@ -97,7 +102,16 @@ void Connection::handleMessage(const std::string& message) {
|
||||
));
|
||||
return;
|
||||
} else {
|
||||
LERROR(fmt::format("Could not parse JSON: '{}'", message));
|
||||
std::string sanitizedString = message;
|
||||
std::transform(
|
||||
message.begin(),
|
||||
message.end(),
|
||||
sanitizedString.begin(),
|
||||
[](const unsigned char& c) {
|
||||
return std::isprint(c) ? c : ' ';
|
||||
}
|
||||
);
|
||||
LERROR(fmt::format("Could not parse JSON: '{}'", sanitizedString));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
|
||||
#include <modules/server/include/jsonconverters.h>
|
||||
|
||||
#include <openspace/json.h>
|
||||
#include <openspace/properties/property.h>
|
||||
#include <openspace/rendering/renderable.h>
|
||||
#include <openspace/scene/scenegraphnode.h>
|
||||
@@ -37,7 +36,7 @@ namespace openspace::properties {
|
||||
void to_json(json& j, const Property& p) {
|
||||
j = {
|
||||
{ "Description", json::parse(p.generateBaseJsonDescription()) },
|
||||
{ "Value", p.jsonValue() }
|
||||
{ "Value", json::parse(p.jsonValue()) }
|
||||
};
|
||||
j["Description"]["description"] = p.description();
|
||||
}
|
||||
@@ -49,6 +48,7 @@ void to_json(json& j, const Property* pP) {
|
||||
void to_json(json& j, const PropertyOwner& p) {
|
||||
j = {
|
||||
{ "identifier", p.identifier() },
|
||||
{ "guiName", p.guiName() },
|
||||
{ "description", p.description() },
|
||||
{ "properties", p.properties() },
|
||||
{ "subowners", p.propertySubOwners() },
|
||||
|
||||
@@ -39,7 +39,6 @@ namespace openspace {
|
||||
void LuaScriptTopic::handleJson(const nlohmann::json& json) {
|
||||
try {
|
||||
std::string script = json.at(ScriptKey).get<std::string>();
|
||||
LDEBUG("Queueing Lua script: " + script);
|
||||
global::scriptEngine.queueScript(
|
||||
std::move(script),
|
||||
scripting::ScriptEngine::RemoteScripting::No
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
#include <openspace/json.h>
|
||||
#include <openspace/engine/globals.h>
|
||||
#include <openspace/properties/property.h>
|
||||
#include <openspace/scripting/scriptengine.h>
|
||||
#include <openspace/query/query.h>
|
||||
#include <openspace/util/timemanager.h>
|
||||
#include <openspace/util/time.h>
|
||||
@@ -37,6 +37,48 @@ namespace {
|
||||
constexpr const char* ValueKey = "value";
|
||||
constexpr const char* _loggerCat = "SetPropertyTopic";
|
||||
constexpr const char* SpecialKeyTime = "__time";
|
||||
|
||||
std::string escapedLuaString(const std::string& str) {
|
||||
std::string luaString;
|
||||
for (const char& c : str) {
|
||||
switch (c) {
|
||||
case '\'':
|
||||
luaString += "\'";
|
||||
break;
|
||||
default:
|
||||
luaString += c;
|
||||
}
|
||||
}
|
||||
return luaString;
|
||||
}
|
||||
|
||||
std::string luaLiteralFromJson(nlohmann::json value) {
|
||||
if (value.is_string()) {
|
||||
return "'" + escapedLuaString(value.get<std::string>()) + "'";
|
||||
} else if (value.is_boolean()) {
|
||||
return value.get<bool>() ? "true" : "false";
|
||||
} else if (value.is_number()) {
|
||||
return std::to_string(value.get<double>());
|
||||
} else if (value.is_array()) {
|
||||
std::string literal = "{";
|
||||
for (nlohmann::json::iterator it = value.begin(); it != value.end(); ++it) {
|
||||
literal += luaLiteralFromJson(it.value()) += ",";
|
||||
}
|
||||
literal.pop_back(); // remove last comma
|
||||
literal += "}";
|
||||
return literal;
|
||||
} else if (value.is_object()) {
|
||||
std::string literal = "{";
|
||||
for (nlohmann::json::iterator it = value.begin(); it != value.end(); ++it) {
|
||||
literal += it.key() + "=" + luaLiteralFromJson(it.value()) += ",";
|
||||
}
|
||||
literal.pop_back(); // remove last comma
|
||||
literal += "}";
|
||||
return literal;
|
||||
}{
|
||||
return "null";
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace openspace {
|
||||
@@ -44,21 +86,20 @@ namespace openspace {
|
||||
void SetPropertyTopic::handleJson(const nlohmann::json& json) {
|
||||
try {
|
||||
const std::string& propertyKey = json.at(PropertyKey).get<std::string>();
|
||||
std::string value = json.at(ValueKey).get<std::string>();
|
||||
|
||||
if (propertyKey == SpecialKeyTime) {
|
||||
Time newTime;
|
||||
newTime.setTime(value);
|
||||
newTime.setTime(json.at(ValueKey).get<std::string>());
|
||||
global::timeManager.setTimeNextFrame(newTime);
|
||||
}
|
||||
else {
|
||||
properties::Property* prop = property(propertyKey);
|
||||
if (prop) {
|
||||
LDEBUG("Setting " + propertyKey + " to " + value + ".");
|
||||
if (!prop->setStringValue(std::move(value))) {
|
||||
LERROR("Failed!");
|
||||
}
|
||||
}
|
||||
nlohmann::json value = json.at(ValueKey);
|
||||
std::string literal = luaLiteralFromJson(value);
|
||||
|
||||
global::scriptEngine.queueScript(
|
||||
"openspace.setPropertyValueSingle(\"" + propertyKey + "\", " + literal + ")",
|
||||
scripting::ScriptEngine::RemoteScripting::Yes
|
||||
);
|
||||
}
|
||||
}
|
||||
catch (const std::out_of_range& e) {
|
||||
@@ -69,6 +110,9 @@ void SetPropertyTopic::handleJson(const nlohmann::json& json) {
|
||||
LERROR("Could not set property -- runtime error:");
|
||||
LERROR(e.what());
|
||||
}
|
||||
catch (...) {
|
||||
LERROR("Could not set property -- unknown error");
|
||||
}
|
||||
}
|
||||
|
||||
bool SetPropertyTopic::isDone() const {
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
/*****************************************************************************************
|
||||
* *
|
||||
* OpenSpace *
|
||||
* *
|
||||
* Copyright (c) 2014-2018 *
|
||||
* *
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
|
||||
* software and associated documentation files (the "Software"), to deal in the Software *
|
||||
* without restriction, including without limitation the rights to use, copy, modify, *
|
||||
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
|
||||
* permit persons to whom the Software is furnished to do so, subject to the following *
|
||||
* conditions: *
|
||||
* *
|
||||
* The above copyright notice and this permission notice shall be included in all copies *
|
||||
* or substantial portions of the Software. *
|
||||
* *
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
|
||||
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
|
||||
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
|
||||
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
|
||||
****************************************************************************************/
|
||||
|
||||
#include <modules/server/include/topics/shortcuttopic.h>
|
||||
|
||||
#include <modules/server/include/connection.h>
|
||||
#include <modules/server/servermodule.h>
|
||||
|
||||
#include <openspace/openspace.h>
|
||||
#include <openspace/engine/globals.h>
|
||||
|
||||
#include <openspace/interaction/shortcutmanager.h>
|
||||
#include <openspace/interaction/keybindingmanager.h>
|
||||
|
||||
namespace {
|
||||
constexpr const char* EventKey = "event";
|
||||
constexpr const char* StartSubscription = "start_subscription";
|
||||
constexpr const char* StopSubscription = "stop_subscription";
|
||||
} // namespace
|
||||
|
||||
using nlohmann::json;
|
||||
|
||||
namespace openspace {
|
||||
|
||||
ShortcutTopic::ShortcutTopic() {}
|
||||
|
||||
ShortcutTopic::~ShortcutTopic() {}
|
||||
|
||||
bool ShortcutTopic::isDone() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<nlohmann::json> ShortcutTopic::shortcutsJson() const {
|
||||
using ShortcutInformation = interaction::ShortcutManager::ShortcutInformation;
|
||||
|
||||
const std::vector<ShortcutInformation>& shortcuts =
|
||||
global::shortcutManager.shortcuts();
|
||||
|
||||
std::vector<nlohmann::json> json;
|
||||
for (const ShortcutInformation& shortcut : shortcuts) {
|
||||
nlohmann::json shortcutJson = {
|
||||
{ "name", shortcut.name },
|
||||
{ "script", shortcut.script },
|
||||
{ "synchronization", static_cast<bool>(shortcut.synchronization) },
|
||||
{ "documentation", shortcut.documentation },
|
||||
{ "guiPath", shortcut.guiPath },
|
||||
};
|
||||
json.push_back(shortcutJson);
|
||||
}
|
||||
|
||||
using KeyInformation = interaction::KeybindingManager::KeyInformation;
|
||||
|
||||
const std::multimap<KeyWithModifier, KeyInformation>& keyBindings =
|
||||
global::keybindingManager.keyBindings();
|
||||
|
||||
for (const std::pair<KeyWithModifier, KeyInformation>& keyBinding : keyBindings) {
|
||||
const KeyWithModifier& k = keyBinding.first;
|
||||
const KeyInformation& info = keyBinding.second;
|
||||
|
||||
nlohmann::json shortcutJson = {
|
||||
{ "key", ghoul::to_string(k.key) },
|
||||
{ "modifiers",
|
||||
{
|
||||
{"shift" , hasKeyModifier(k.modifier, KeyModifier::Shift) },
|
||||
{"control" , hasKeyModifier(k.modifier, KeyModifier::Control) },
|
||||
{"alt" , hasKeyModifier(k.modifier, KeyModifier::Alt) },
|
||||
{"super" , hasKeyModifier(k.modifier, KeyModifier::Super) }
|
||||
}
|
||||
},
|
||||
{ "name", info.name },
|
||||
{ "script", info.command },
|
||||
{ "synchronization", static_cast<bool>(info.synchronization) },
|
||||
{ "documentation", info.documentation },
|
||||
{ "guiPath", info.guiPath },
|
||||
};
|
||||
json.push_back(shortcutJson);
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
void ShortcutTopic::sendData() const {
|
||||
nlohmann::json data = {
|
||||
{"shortcuts", shortcutsJson()}
|
||||
};
|
||||
_connection->sendJson(wrappedPayload(data));
|
||||
}
|
||||
|
||||
void ShortcutTopic::handleJson(const nlohmann::json& input) {
|
||||
const std::string& event = input.at(EventKey).get<std::string>();
|
||||
if (event == StartSubscription) {
|
||||
// TODO: Subscribe to shortcuts and keybindings
|
||||
// shortcutManager.subscribe(); ...
|
||||
} else if (event == StopSubscription) {
|
||||
// TODO: Unsubscribe to shortcuts and keybindings
|
||||
// shortcutManager.unsubscribe(); ...
|
||||
return;
|
||||
}
|
||||
sendData();
|
||||
}
|
||||
|
||||
} // namespace openspace
|
||||
@@ -63,14 +63,11 @@ void SubscriptionTopic::handleJson(const nlohmann::json& json) {
|
||||
const std::string& event = json.at(EventKey).get<std::string>();
|
||||
|
||||
if (event == StartSubscription) {
|
||||
LDEBUG(fmt::format("Subscribing to property '{}'", key));
|
||||
|
||||
_prop = property(key);
|
||||
if (_prop) {
|
||||
_requestedResourceIsSubscribable = true;
|
||||
_isSubscribedTo = true;
|
||||
auto onChange = [this, k = std::move(key)]() {
|
||||
LDEBUG("Updating subscription '" + k + "'.");
|
||||
_connection->sendJson(wrappedPayload(_prop));
|
||||
};
|
||||
_onChangeHandle = _prop->onChange(onChange);
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace {
|
||||
constexpr const char* UnsubscribeEvent = "stop_subscription";
|
||||
constexpr const char* CurrentTimeKey = "currentTime";
|
||||
constexpr const char* DeltaTimeKey = "deltaTime";
|
||||
constexpr const std::chrono::milliseconds TimeUpdateInterval(100);
|
||||
constexpr const std::chrono::milliseconds TimeUpdateInterval(50);
|
||||
} // namespace
|
||||
|
||||
using nlohmann::json;
|
||||
@@ -87,10 +87,15 @@ void TimeTopic::handleJson(const nlohmann::json& json) {
|
||||
else if (requestedKey == DeltaTimeKey) {
|
||||
_deltaTimeCallbackHandle = global::timeManager.addDeltaTimeChangeCallback(
|
||||
[this]() {
|
||||
_connection->sendJson(deltaTime());
|
||||
if (_timeCallbackHandle != UnsetOnChangeHandle) {
|
||||
_connection->sendJson(currentTime());
|
||||
_lastUpdateTime = std::chrono::system_clock::now();;
|
||||
std::chrono::system_clock::time_point now =
|
||||
std::chrono::system_clock::now();
|
||||
if (now - _lastUpdateTime > TimeUpdateInterval) {
|
||||
_connection->sendJson(deltaTime());
|
||||
if (_timeCallbackHandle != UnsetOnChangeHandle) {
|
||||
_connection->sendJson(currentTime());
|
||||
_lastUpdateTime = std::chrono::system_clock::now();
|
||||
}
|
||||
_lastUpdateTime = now;
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -108,7 +113,11 @@ json TimeTopic::currentTime() {
|
||||
}
|
||||
|
||||
json TimeTopic::deltaTime() {
|
||||
json timeJson = { { "deltaTime", global::timeManager.deltaTime() } };
|
||||
json timeJson = {
|
||||
{ "deltaTime", global::timeManager.deltaTime() },
|
||||
{ "targetDeltaTime", global::timeManager.targetDeltaTime() },
|
||||
{ "isPaused", global::timeManager.isPaused() },
|
||||
};
|
||||
return wrappedPayload(timeJson);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
/*****************************************************************************************
|
||||
* *
|
||||
* OpenSpace *
|
||||
* *
|
||||
* Copyright (c) 2014-2018 *
|
||||
* *
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
|
||||
* software and associated documentation files (the "Software"), to deal in the Software *
|
||||
* without restriction, including without limitation the rights to use, copy, modify, *
|
||||
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
|
||||
* permit persons to whom the Software is furnished to do so, subject to the following *
|
||||
* conditions: *
|
||||
* *
|
||||
* The above copyright notice and this permission notice shall be included in all copies *
|
||||
* or substantial portions of the Software. *
|
||||
* *
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
|
||||
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
|
||||
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
|
||||
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
|
||||
****************************************************************************************/
|
||||
|
||||
#include "modules/server/include/topics/versiontopic.h"
|
||||
|
||||
#include <modules/server/include/connection.h>
|
||||
#include <modules/server/servermodule.h>
|
||||
|
||||
#include <openspace/openspace.h>
|
||||
#include <openspace/engine/globals.h>
|
||||
|
||||
namespace {
|
||||
constexpr const char* _loggerCat = "VersionTopic";
|
||||
} // namespace
|
||||
|
||||
using nlohmann::json;
|
||||
|
||||
namespace openspace {
|
||||
|
||||
VersionTopic::VersionTopic() {
|
||||
}
|
||||
|
||||
VersionTopic::~VersionTopic() {
|
||||
}
|
||||
|
||||
bool VersionTopic::isDone() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
void VersionTopic::handleJson(const nlohmann::json&) {
|
||||
nlohmann::json versionJson = {
|
||||
{
|
||||
"openSpaceVersion",
|
||||
{
|
||||
{"major", OPENSPACE_VERSION_MAJOR},
|
||||
{"minor", OPENSPACE_VERSION_MINOR},
|
||||
{"patch", OPENSPACE_VERSION_PATCH}
|
||||
}
|
||||
},
|
||||
{
|
||||
"socketApiVersion",
|
||||
{
|
||||
{"major", SOCKET_API_VERSION_MAJOR},
|
||||
{"minor", SOCKET_API_VERSION_MINOR},
|
||||
{"patch", SOCKET_API_VERSION_PATCH}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
_connection->sendJson(
|
||||
wrappedPayload(versionJson)
|
||||
);
|
||||
}
|
||||
|
||||
} // namespace openspace
|
||||
Reference in New Issue
Block a user