I managed to get DukTape working in my GLUT project (it is able to run inline javascript with duk_eval_string();). Is it possible to display a static html canvas with javascript graphics in a C++ GLUT window using DukTape?
Using the HTML5 canvas drawing methods is not possible in Duktape.
Duktape is a Javascript engine, what means that it allows you to execute ES5/5.1 compliant code. Displaying a HTML canvas is a task Duktape can't do.
If you ultimately want to achieve this, try searching for a library to achieve such a task, maybe look at the Firefox source.
If you want to do it completely from scratch you would need to add C function bindings (Example at duktape.org/) for every draw method you want. An example would be like this:
// C/C++ code:
// C function to be used in the Javascript engine
int js_draw_rect(duk_context *ctx) {
// get parameters from javascript function call
int pos_x = duk_get_number(ctx, -4);
int pos_y = duk_get_number(ctx, -3);
...
// C/C++ code to draw the rectangle (in your case probably GLUT)
draw_rectangle(pos_x, pos_y, ...);
return 0;
}
int main(void) {
duk_context *ctx;
...
// this snippet adds a binding for the function 'js_draw_rect' so it can be called from Javascript code
duk_push_global_object(ctx);
duk_push_c_function(ctx, js_draw_rect, 4/*number of args the JS function has*/);
duk_put_prop_string(ctx, -2 /*idx:global*/, "drawRect"/*name of function in JS environment*/);
duk_pop(ctx);
}
// Javascript code:
drawRect(50, 50, 100, 200);
...
This method allows you to create C/C++ functions which handle all drawing and later bind them all to the Javascript engine so they can be called in JS.
Related
As I'm new into javascript/HTML and recently I started a project using Qt QWebEngineView. I've been stuck for some time on finding the best way to share data from the C++ program to Javascript. So far the only way I was able to send data to the Javascript program is by using the QWebEnginePage::runJavaScript function. I also have seen that there is the possibility of using QWebChannels described here but I prefer the QWebEnginePage::runJavaScript for its simplicity.
The only issue I've had so far with the runJavaScript method has been that in order to write a variable, this needs to be defined in the HTML file, I'm actually not 100% sure if this is the only way to do it but it has been the only way it worked for me. My current scenario looks something like this:
In the HTML file:
...
<div id="latitude" ></div>
<div id="longitude"></div>
<div id="heading" "></div>
...
In the C++ file:
...
double Latitude = 44.244; Longitude = 10.3; Heading = 90;
QString jsQuery = QObject::tr(
"document.getElementById('latitude').innerHTML =%1; "
"document.getElementById('longitude').innerHTML =%2; "
"document.getElementById('heading').innerHTML =%3;"
).arg(Latitude).arg(Longitude).arg(Heading));
mapWebView->page()->runJavaScript(jsQuery);
...
With this setup, I'm able to write the variables in the Javascript/HTML side from the C++ code. Because with this solution I need to create as many individual variables on the HTML file for each value I want to send, I wanted to ask if it is possible to instead of using individual variables use a class object or a JavaScript Object. I create a class with some methods to write the class members like the one below:
In the js file:
...
export default class PositionState{
setPosition(latitude = 0.0, longitude = 0.0, heading = 0.0){
this.Latitude = latitude;
this.Longitude = longitude;
this.Heading = heading;
}
getLatitude(){
return this.Latitude;
}
getLongitude(){
return this.Longitude;
}
getHeading(){
return this.Heading;
}
}
var obj = new PositionState();
...
With this solution, if I create an object of the PositionState class and call the function obj.setPosition(44,10.45) from the javascript file the object's class members are set correctly, But if I try it from the C++ I get some errors.
double Latitude = 44.244; Longitude = 10.3; Heading = 90;
Qstring jsQuery = QObject::tr(
"obj.setPosition(%1, %2, %3);"
).arg(Latitude).arg(Longitude).arg(Heading));
mapWebView->page()->runJavaScript(jsQuery);
If only define the obj in the Javascript file I get the error js: Uncaught ReferenceError: obj is not defined. And if I define a variable in the HTML file with the Id="obj" and run the same script I get the error js: Uncaught TypeError: obj.setPosition is not a function, the error occurs even if instead of just obj.setPosition i use document.getElementById('obj').setPosition.
So for what I get with my little to none HTML/Javascript knowledge is that the HTML file is not aware of my class definition, thus not recognizing the setPosition method. So my question is if there's a way to from the C++ code write the class object.
I also tried using a JavaScript Object like var Position = {Latitude: 0, Longitude: 0, Heading: 0} and from the C++ code run the a script with the QString Position = {Latitude: 40, Longitude: 9, Heading: 20}; but was also no able to change the Position object properties.
Any help will be really appreciated, thanks.
Honestly, you seem to be overcomplicating things...
Your "simplier" approach is nowhere as simple as using the QWebChannel to share objects between the JS and the C++ world. Additionally, you are losing a lot of functionality by going another way: connecting to Qt signals in JS, overloading methods, reading and setting properties directly from JS...
Just use the QWebChannel to register QObjects into the QWebEnginePage, load QWebChannel into your HTML page, setup the connection, and that's it!
This article has an excelent and simple explanation (with code snippets) on how to do this.
There is no need for you to reinvent the wheel.
How can I check or receive a message when the a Unity WebGL app is finished with loading and ready for use? What I want is to run a JavaScript function of my Webinterface after the WebGL App is ready.
If you're looking into using a front-end framework, you might want to look into this library I've made. It adds functionality for two way communication between your webpage and Unity content. To send messages from your Unity content, back to the web page, you can create JavaScript listeners which can be triggered from your CSharp code, and even pass data.
// Create a new Unity Content object (using the library)
this.unityContent = new UnityContent(
"MyGame/Build.json",
"MyGame/UnityLoader.js" );
// Then add a new listener to the object.
this.unityContent.on("GameOver", score => {
// Do somehting...
});
In order to trigger the event we've just created, you have to create a JSLib file to bind the communication. The listener registered in React is now available in the ReactUnityWebGL object in any JSLib file. You can now create a JSLib file and get started. We're going to create a new JSLib file in the following directory. Assets/Plugins/WebGL/MyPlugin.jslib.
mergeInto(LibraryManager.library, {
// Create a new function with the same name as
// the event listeners name and make sure the
// parameters match as well.
GameOver: function(score) {
// Within the function we're going to trigger
// the event within the ReactUnityWebGL object
// which is exposed by the library to the window.
ReactUnityWebGL.GameOver(score);
}
});
Finally, to trigger to event within your CSharp code. We have to import the JSLib as following.
using UnityEngine;
public class GameController : MonoBehaviour {
// Import the JSLib as following. Make sure the
// names match with the JSLib file we've just created.
[DllImport("__Internal")]
private static extern void GameOver (int score);
// Then create a function that is going to trigger
// the imported function from our JSLib.
public void GameOver (int score) {
GameOver (score);
}
}
I use a different approach. This is especially helpful because you can define callbacks in whatever part of your Unity code you wish i.e. you can react to async events.
//in your html|js
function OnAppReady(){
console.log("### application is loaded")
}
//in Unity
void Start()
{
//js callback when application is loaded
Application.ExternalEval("OnAppReady()");
}
I've found a solution that works for me. There is a JavaScript file UnityProgress.js in the Build folder of your WebGL build. Inside you can find a variable progresswhich will be set to 1.0 after the loading / downloading progress is finished. You can place your code after that if-statement at the bottom to run your JavaScript code (Don't forget the brackets^^).
But there is some initialization time that the Unity App needs to start. So you probably have to set a delay time. For me 2500ms worked well.
function UnityProgress(gameInstance, progress) {
...
if (progress == 1.0) {
gameInstance.logo.style.display = gameInstance.progress.style.display = "none";
// call of my function:
console.log("#### WebGL is ready now ####");
setTimeout(function() {
myFunction();
}, 2500);
}
}
Maybe it is stupid question (I am newbie to C++, just wanted to use it as library for android), but I am not able to run evaluation of some JS multiple times.
I have started with "hello world" tutorial. But then I have wanted simple thing, re-run main (just wrap content of tutorial code into function and run it twice in newly empty main.
This is what I got:
#
# Fatal error in ../src/isolate.cc, line 1868
# Check failed: thread_data_table_.
#
==== C stack trace ===============================
1: 0xa890b9
2: 0x6a22fc
3: 0x42694f
4: 0x405f66
5: 0x405ec7
6: __libc_start_main
7: 0x405dc9
Illegal instruction (core dumped)
This cames after creating new isolate
Isolate* isolate = Isolate::New(create_params);
Well, what I should do? Am I using wrong construct or so? Should I close/delete/clear something more?
In bigger view I just want to do evaluate function, that can be triggered multiple times, and beside that also run multiple js snipets in same context (how to split this function?).
Any idea?
UPDATE:
Ok, lets say that the main can be split into three logical parts:
init
int main(int argc, char* argv[]) {
// Initialize V8.
V8::InitializeICU();
V8::InitializeExternalStartupData(argv[0]);
Platform* platform = platform::CreateDefaultPlatform();
V8::InitializePlatform(platform);
V8::Initialize();
// Create a new Isolate and make it the current one.
ArrayBufferAllocator allocator;
Isolate::CreateParams create_params;
create_params.array_buffer_allocator = &allocator;
evaluation
Isolate* isolate = Isolate::New(create_params);
{
Isolate::Scope isolate_scope(isolate);
// Create a stack-allocated handle scope.
HandleScope handle_scope(isolate);
// Create a new context.
Local<Context> context = Context::New(isolate);
// Enter the context for compiling and running the hello world script.
Context::Scope context_scope(context);
// Create a string containing the JavaScript source code.
Local<String> source =
String::NewFromUtf8(isolate, "'Hello' + ', World!'",
NewStringType::kNormal).ToLocalChecked();
// Compile the source code.
Local<Script> script = Script::Compile(context, source).ToLocalChecked();
// Run the script to get the result.
Local<Value> result = script->Run(context).ToLocalChecked();
// Convert the result to an UTF8 string and print it.
String::Utf8Value utf8(result);
printf("%s\n", *utf8);
}
isolate->Dispose();
and clean
// Dispose and tear down V8.
V8::Dispose();
V8::ShutdownPlatform();
delete platform;
return 0;
Now as I said before if I run main consists of init->evaluation->clean twice, that mean init->evaluation->clean->init->evaluation->clean, then the error occurs. I have figured out, that if I extract evaluation part into separate function I can run it multiple times e.g. as init->(evaluation){2}->clean
Is that how should it work? Next step is to divide this main into tree separate function that mean I have to have static member with platform? Could it cause leak somehow?
NOTE: that I want to run it from android, that mean e.g. click in UI, propagate js source to C via JNI and then call c++ V8, which is already initialized or not. hm?
Prefered way is to have "blackbox", but if I have to hold platform, so be it. It maybe could be also faster without re-initialization of V8, right?
UPDATE 2:
Well, still have problems with splitting evaluation part to achieve multiple runs in same isolate/context.
I have splitted it after creating context with stored isolate and context, but with no luck. When in second part try to create source string it fails, probably because of using stored isolate (something with isolate scope I guess).
:(
My assumption as I introduced in UPDATE1 was correct. That part works well.
According to UPDATE2 I have splitted evaluation part into two.
First for initialize isolate and context:
mIsolate = Isolate::New(mCreate_params);
Isolate::Scope isolate_scope(mIsolate);
{
// Create a stack-allocated handle scope.
HandleScope handle_scope(mIsolate);
v8::Handle<v8::ObjectTemplate> global = v8::ObjectTemplate::New(mIsolate);
// Bind the global 'print' function to the C++ Print callback.
global->Set(v8::String::NewFromUtf8(mIsolate, "print"), v8::FunctionTemplate::New(mIsolate, Print));
// Create a new context.
mContext = Context::New(mIsolate, NULL, global);
Persistent<Context, CopyablePersistentTraits<Context>> persistent(mIsolate, mContext);
mContext_persistent = persistent;
}
and second that will run js in same context:
Isolate::Scope isolate_scope(mIsolate);
{
HandleScope handle_scope(mIsolate);
mContext = Local<Context>::New(mIsolate, mContext_persistent);
// Enter the context for compiling and running the hello world script.
Context::Scope context_scope(mContext);
{
// Create a string containing the JavaScript source code.
Local<String> source =
String::NewFromUtf8(mIsolate, js_source, NewStringType::kNormal).ToLocalChecked();
// Compile the source code.
Local<Script> script = Script::Compile(mContext, source).ToLocalChecked();
TryCatch trycatch(mIsolate);
// Run the script to get the result.
v8::Local<v8::Value> result;
if(!script->Run(mContext).ToLocal(&result)){
v8::String::Utf8Value exception_str(trycatch.Exception());
dprint(*exception_str);
}else{
if(!result->IsUndefined()){
String::Utf8Value utf8(result);
dprint(*utf8);
}
}
}
}
Well the code works very well on linux, but I still have some issues when I try to run first part for the second time (create new context) on android:
A/art: art/runtime/thread.cc:986] pthread_getschedparam failed for DumpState: No such process
A/art: art/runtime/base/mutex.cc:485] Unexpected state_ 0 in unlock for logging lock
But that's another question I guess. Peace.
Did you initialize v8 more than once?
v8::V8::Initialize() this method should be called once per process.
deep into project source file "v8/src/v8.cc", you will find the prove
bool V8::Initialize() {
InitializeOncePerProcess();
return true;
}
I'm using the new WebEngine to play around and learn.
I've been trying to find some similar methods found using Qt WebKit: addToJavaScriptWindowObject()
I found that using Qt WebEngine, I have to use the QWebChannel to register functions to the JavaScript window object. If this is correct, it takes me to the following question.
I've installed Qt 5.4.0 on my computer. I noticed that qwebchannel.js is not found in the SDK installed on my computer. I found it on the Git source.
If I have a Qt native desktop application with a QWebEnginePage and QWebEngineView, what do I need to be able to register functions on the JavaScript window object?
My desktop application navigates automatically to a http page that I have created. So I have access to the content connected to the QWebEngineView.
What are the steps to take so I can make this work?
In Qt5.6, if you want to make C++ part and JavaScript to communicate, the only way to do it is using QWebChannel on a QWebEngineView, as you stated. You do it this way in the .cpp file:
m_pView = new QWebEngineView(this);
QWebChannel * channel = new QWebChannel(page);
m_pView->page()->setWebChannel(channel);
channel->registerObject(QString("TheNameOfTheObjectUsed"), this);
Here, you just say that you register an object named TheNameOfTheObjectUsed that will be available on the JS side. Now, this is the part of code to use in the JS side :
new QWebChannel(qt.webChannelTransport, function (channel) {
// now you retrieve your object
var JSobject = channel.objects.TheNameOfTheObjectUsed;
});
Now, if you want to retrieve some properties of the class in the JS side, you need to have a method on the C++ side which returns a string, an integer, a long... This is what it looks like on the C++ side, in your .h:
Q_INVOKABLE int getInt();
Q_PROPERTY(int myIntInCppSide READ getInt);
And now, you get the int like this on the JS side :
var myIntInJSside= JSobject.myIntInCppSide;
This is a very simple explanation, and I recommend you to watch this video which was very useful to me. Also, you might want to read more about the JavaScript API provided by QWebChannel, as well as the documentation about QWebChannel.
Hope that helps!
I will summarize your questions as following:
Do I need QWebChannel to register JavaScript functions in the WebEngine?
Where can I find QWebChannel.js
How to communicate JS to C++ and C++ to JS
First, let take a simple code to play with:
#include <QApplication>
#include <QDebug>
#include <QWebEngineView>
#include <QWebChannel>
// ... DEFINITIONS HERE
auto main( int argn, char* argv[] )-> int
{
QApplication app(argn, argv);
QWebEngineView browser;
browser.resize(QSize(800,600));
browser.show();
browser.load(QUrl("http://www.wikipedia.org"));
// .. SETUP HERE
QObject::connect(&browser, &QWebEngineView::loadFinished, [&browser](bool ok)
{
qDebug()<<"Load Finished " << ok;
// TEST CODE HERE
));
return app.exec();
}
Explanation: This code creates a Qt application, creates a QWebEngineView and set some minimal properties to make it visible.
A page from 'Wikipedia' is loaded inside and a signal/slot event is connected to print some log when the page is finally loaded.
How to call JS functions from C++ ?
You can simply call JS using QWebEnginePage::runJavaScript as following. Add this code to the TEST CODE HERE.
QString code = QStringLiteral(
R"DELIM(
var links = document.getElementsByTagName('a');
for ( var i=0; i<links.length; ++i)
{
links[i].style.backgroundColor = 'yellow';
};
)DELIM");
browser.page()->runJavaScript(code, 42);
Explanation: This code execute some JS into the browser, on a context ID 42, avoiding collision with the default context of the page ID 0. The script change the background-color of each link to yellow.
How to call C++ from JS?
In this case, we need the QWebChannel mechanism to register C++ objects into JavaScript.
First, let create the C++ interface, callable from JS (in DEFINITION):
class JsInterface: public QObject
{
Q_OBJECT
public:
/// Log, for debugging
Q_INVOKABLE void log(const QString& str) const
{
qDebug() << "LOG from JS: " << str;
}
};
#include "main.moc"
Explanation: This code declare and define a QObject class with a simple log function inside. It is important to declare the function Q_INVOKABLE otherwise JavaScript can not find it!. As the declaration is inside the same file as the rest of the code, we include the auto-moc file from QT after (it is main.moc because my file is main.cpp).
Create a function in DEFINITION which return the JavaScript QWebChannel.js content. The content of QWebChannel.js can be found in your QT library (./5.12.2/Src/qtwebchannel/examples/webchannel/shared/qwebchannel.js or ./Examples/Qt-5.12.2/webchannel/shared/qwebchannel.js). You are free to load this directly in your page.
In DECLARATION section, append:
QString qWebChannelJs()
{
return R"DELIMITER(
// COPY HERE ALL THE FILE
)DELIMITER";
}
And we inject it in our code (Append it to TEST CODE HERE section):
browser.page()->runJavaScript(qWebChannelJs(), 42);
We need to setup the QWebChannel in C++ side (SETUP section):
QWebChannel channel;
JsInterface jsInterface;
browser.page()->setWebChannel(&channel, 42);
channel.registerObject(QString("JsInterface"), &jsInterface);
Explanation: We create a channel, the JsInterface object and register them into the browser. We need to use the same context id 42 (but could be another other number between 0 and 255).
Finally, in our JS code, we access the channel and call the function of the interface (append to TEST CODE section):
QString code2 = QStringLiteral(
R"DELIM(
window.webChannel = new QWebChannel(qt.webChannelTransport, function( channel)
{
var cpp = channel.objects.JsInterface;
cpp.log("Hello from JavaScript");
});
)DELIM");
browser.page()->runJavaScript(code2, 42);
Considerations
It is worth to mention that any call from C++ to JavaScript or from JavaScript to C++ goes through an Inter-Process-Communication (IPC) which is asynchronous. This means that runJavaScript returns before the JavaScript is executed, and that JavaScript returns before the C++ logis executed.
Full code
#include <QApplication>
#include <QDebug>
#include <QWebEngineView>
#include <QWebChannel>
QString qWebChannelJs()
{
return R"DELIMITER(
// TODO INSERT JS code here
)DELIMITER";
}
class JsInterface: public QObject
{
Q_OBJECT
public:
/// Log, for debugging
Q_INVOKABLE void log(const QString& str) const
{
qDebug() << "LOG from JS: " << str;
}
};
#include "main.moc"
auto main( int argn, char* argv[] )-> int
{
QApplication app(argn, argv);
QWebEngineView browser;
browser.resize(QSize(800,600));
browser.show();
browser.load(QUrl("http://www.wikipedia.org"));
// .. SETUP HERE
QWebChannel channel;
JsInterface jsInterface;
browser.page()->setWebChannel(&channel, 42);
channel.registerObject(QString("JsInterface"), &jsInterface);
QObject::connect(&browser, &QWebEngineView::loadFinished, [&browser](bool ok)
{
qDebug()<<"Load Finished " << ok;
// TEST CODE HERE
QString code = QStringLiteral(
R"DELIM(
var links = document.getElementsByTagName('a');
for ( var i=0; i<links.length; ++i)
{
links[i].style.backgroundColor = 'yellow';
};
)DELIM");
browser.page()->runJavaScript(code, 42);
browser.page()->runJavaScript(qWebChannelJs(), 42);
QString code2 = QStringLiteral(
R"DELIM(
window.webChannel = new QWebChannel(qt.webChannelTransport, function( channel)
{
var cpp = channel.objects.JsInterface;
cpp.log("Hello from JavaScript");
});
)DELIM");
browser.page()->runJavaScript(code2, 42);
});
return app.exec();
}
Related topics:
How to setup QWebChannel JS API for use in a QWebEngineView?
External documentation:
https://doc.qt.io/qt-5/qwebengineview.html
https://doc.qt.io/qt-5/qwebchannel.html
https://doc.qt.io/qt-5/qtwebengine-webenginewidgets-contentmanipulation-example.html
Qt has documentation on this now:
Qt WebChannel Standalone Example
You have to add a QWebSocketServer to your cpp app that the QWebEngineView's HTML/Javascript will connect to using a WebSocket. Then use QWebChannel for two way communication.
An alternative and much simpler way of communication with the page is to use runJavaScript function:
view->page()->runJavaScript("alert('Hello from C++');");
It has its limitations: the call must be initiated from the C++ side and you can get only synchronous response from JS. But there is an upside too: no modification of the underlying webpage is necessary.
Currently opened webpage can be accessed using QWebEngineView::page() function, as in the example above. During the navigation, the browser doesn't change the page until the next one is received from the network, so this function returns valid page object at any time. But your JS may still interrupt new page loading in a way that you'll appear in the document.readyState == 'loading' where the DOM tree is not yet constructed and some scripts on the page might not have been run yet. In this case you should wait for the DOMContentLoaded event.
I noticed that if I execute a JavaScript script using the mongo command, the script can treat a cursor object as if it was an array.
var conn = new Mongo('localhost:27017');
var db = conn.getDB('learn');
db.test.remove({});
db.test.insert({foo: 'bar'});
var cur = db.test.find();
print(cur[0].foo); //prints: bar
print(cur[1]); // prints: undefined
This seems like it should be beyond the capabilities of the JavaScript language, since there is no way to "overload the subscript operator". So how does this actually work?
As documentation says, it is special ability of driver. It automagicly converts cursor[0] to cursor.toArray()[0]. You can prove it by overriding toArray() with print function or new Error().stack to get callstack back. Here it is:
at DBQuery.a.toArray ((shell):1:32)
at DBQuery.arrayAccess (src/mongo/shell/query.js:290:17)
at (shell):1:2
As you can see, indexing calls arrayAccess. How? Here we have a dbQueryIndexAccess function, which calls arrayAccess.
v8::Handle<v8::Value> arrayAccess = info.This()->GetPrototype()->ToObject()->Get(
v8::String::New("arrayAccess"));
...
v8::Handle<v8::Function> f = arrayAccess.As<v8::Function>();
...
return f->Call(info.This(), 1, argv);
And here we have a code, which sets indexed property handler to this function. WOW, v8 API gives us ability to add this handler!
DBQueryFT()->InstanceTemplate()->SetIndexedPropertyHandler(dbQueryIndexAccess);
... and injects it into JS cursor class, which is defined originaly in JS.
injectV8Function("DBQuery", DBQueryFT(), _global);
Tl;dr: It is hacked in C++ source code of mongo shell.