diff --git a/rtkwebview/json.cpp b/rtkwebview/json.cpp new file mode 100644 index 0000000..571eaa9 --- /dev/null +++ b/rtkwebview/json.cpp @@ -0,0 +1,505 @@ +#include "json.h" + +using std::map; +using std::deque; +using std::string; +using std::enable_if; +using std::initializer_list; +using std::is_same; +using std::is_convertible; +using std::is_integral; +using std::is_floating_point; + +std::string json_escape(const string &str) { + string output; + for( unsigned i = 0; i < str.length(); ++i ) + switch( str[i] ) { + case '\"': output += "\\\""; break; + case '\\': output += "\\\\"; break; + case '\b': output += "\\b"; break; + case '\f': output += "\\f"; break; + case '\n': output += "\\n"; break; + case '\r': output += "\\r"; break; + case '\t': output += "\\t"; break; + default : output += str[i]; break; + } + return std::move( output ); +} + +JSON::JSON() : Internal(), Type( Class::Null ){} + +JSON::JSON(std::initializer_list list) + : JSON() +{ + SetType( Class::Object ); + for( auto i = list.begin(), e = list.end(); i != e; ++i, ++i ) + operator[]( i->toString() ) = *std::next( i ); +} + +JSON::JSON(JSON &&other) + : Internal( other.Internal ) + , Type( other.Type ) +{ other.Type = Class::Null; other.Internal.Map = nullptr; } + +JSON::JSON(const JSON &other) { + switch( other.Type ) { + case Class::Object: + Internal.Map = + new map( other.Internal.Map->begin(), + other.Internal.Map->end() ); + break; + case Class::Array: + Internal.List = + new deque( other.Internal.List->begin(), + other.Internal.List->end() ); + break; + case Class::String: + Internal.String = + new string( *other.Internal.String ); + break; + default: + Internal = other.Internal; + } + Type = other.Type; +} + +JSON JSON::Make(Class type) { + JSON ret; ret.SetType( type ); + return ret; +} + +JSON &JSON::operator[](unsigned int index) { + SetType( Class::Array ); + if( index >= Internal.List->size() ) Internal.List->resize( index + 1 ); + return Internal.List->operator[]( index ); +} + +JSON &JSON::at(const std::string &key) { + return operator[]( key ); +} + +const JSON &JSON::at(const std::string &key) const { + return Internal.Map->at( key ); +} + +JSON &JSON::at(unsigned int index) { + return operator[]( index ); +} + +const JSON &JSON::at(unsigned int index) const { + return Internal.List->at( index ); +} + +int JSON::length() const { + if( Type == Class::Array ) + return Internal.List->size(); + else + return -1; +} + +bool JSON::hasKey(const std::string &key) const { + if( Type == Class::Object ) + return Internal.Map->find( key ) != Internal.Map->end(); + return false; +} + +int JSON::size() const { + if( Type == Class::Object ) + return Internal.Map->size(); + else if( Type == Class::Array ) + return Internal.List->size(); + else + return -1; +} + +JSON::Class JSON::JSONType() const { return Type; } + +bool JSON::IsNull() const { return Type == Class::Null; } + +string JSON::toString() const { bool b; return std::move( toString( b ) ); } + +string JSON::toString(bool &ok) const { + ok = (Type == Class::String); + return ok ? std::move( json_escape( *Internal.String ) ): string(""); +} + +double JSON::toFloat() const { bool b; return toFloat( b ); } + +double JSON::toFloat(bool &ok) const { + ok = (Type == Class::Floating); + return ok ? Internal.Float : 0.0; +} + +long JSON::toInt() const { bool b; return toInt( b ); } + +long JSON::toInt(bool &ok) const { + ok = (Type == Class::Integral); + return ok ? Internal.Int : 0; +} + +bool JSON::toBool() const { bool b; return toBool( b ); } + +bool JSON::toBool(bool &ok) const { + ok = (Type == Class::Boolean); + return ok ? Internal.Bool : false; +} + +JSON::JSONWrapper > JSON::ObjectRange() { + if( Type == Class::Object ) + return JSONWrapper>( Internal.Map ); + return JSONWrapper>( nullptr ); +} + +JSON::JSONWrapper > JSON::ArrayRange() { + if( Type == Class::Array ) + return JSONWrapper>( Internal.List ); + return JSONWrapper>( nullptr ); +} + +JSON::JSONConstWrapper > JSON::ObjectRange() const { + if( Type == Class::Object ) + return JSONConstWrapper>( Internal.Map ); + return JSONConstWrapper>( nullptr ); +} + +JSON::JSONConstWrapper > JSON::ArrayRange() const { + if( Type == Class::Array ) + return JSONConstWrapper>( Internal.List ); + return JSONConstWrapper>( nullptr ); +} + +string JSON::dump(int depth, const std::string &tab) const { + std::string pad = ""; + for( int i = 0; i < depth; ++i, pad += tab ); + + switch( Type ) { + case Class::Null: + return "null"; + case Class::Object: { + std::string s = "{ "; + bool skip = true; + for( auto &p : *Internal.Map ) { + if( !skip ) s += ", "; + s += ( pad + "\"" + p.first + "\" : " + p.second.dump( depth + 1, tab ) ); + skip = false; + } + s += ( " " + pad.erase( 0, 2 ) + "}" ) ; + return s; + } + case Class::Array: { + std::string s = "["; + bool skip = true; + for( auto &p : *Internal.List ) { + if( !skip ) s += ", "; + s += p.dump( depth + 1, tab ); + skip = false; + } + s += "]"; + return s; + } + case Class::String: + return "\"" + json_escape( *Internal.String ) + "\""; + case Class::Floating: + return std::to_string( Internal.Float ); + case Class::Integral: + return std::to_string( Internal.Int ); + case Class::Boolean: + return Internal.Bool ? "true" : "false"; + default: + return ""; + } + return ""; +} + +void JSON::SetType(Class type) { + if( type == Type ) + return; + + ClearInternal(); + + switch( type ) { + case Class::Null: Internal.Map = nullptr; break; + case Class::Object: Internal.Map = new std::map(); break; + case Class::Array: Internal.List = new std::deque(); break; + case Class::String: Internal.String = new std::string(); break; + case Class::Floating: Internal.Float = 0.0; break; + case Class::Integral: Internal.Int = 0; break; + case Class::Boolean: Internal.Bool = false; break; + } + + Type = type; +} + +void JSON::ClearInternal() { + switch( Type ) { + case Class::Object: delete Internal.Map; break; + case Class::Array: delete Internal.List; break; + case Class::String: delete Internal.String; break; + default:; + } +} + +JSON &JSON::operator[](const std::string &key) { + SetType( Class::Object ); return Internal.Map->operator[]( key ); +} + +JSON &JSON::operator=(const JSON &other) { + + if (&other == this) { return *this; } + + ClearInternal(); + switch( other.Type ) { + case Class::Object: + Internal.Map = + new map( other.Internal.Map->begin(), + other.Internal.Map->end() ); + break; + case Class::Array: + Internal.List = + new deque( other.Internal.List->begin(), + other.Internal.List->end() ); + break; + case Class::String: + Internal.String = + new string( *other.Internal.String ); + break; + default: + Internal = other.Internal; + } + Type = other.Type; + return *this; +} + +JSON &JSON::operator=(JSON &&other) { + ClearInternal(); + Internal = other.Internal; + Type = other.Type; + other.Internal.Map = nullptr; + other.Type = Class::Null; + return *this; +} + +JSON Array() { + return std::move( JSON::Make( JSON::Class::Array ) ); +} + +//JSON Object() { return std::move(JSON::Make(JSON::Class::Object)); } + +JSON Object() { + return std::move( JSON::Make( JSON::Class::Object ) ); +} + +std::ostream &operator<<(std::ostream &os, const JSON &json) { + os << json.dump(); + return os; +} + +// Private functions. + +static JSON parse_next( const std::string &, size_t &, std::function ); + + +static void consume_ws( const std::string &str, size_t &offset ) { + while( isspace( str[offset] ) ) ++offset; +} + +static JSON parse_object( const string &str, size_t &offset, std::function on_error ) { + JSON Object = JSON::Make( JSON::Class::Object ); + + ++offset; + consume_ws( str, offset ); + if( str[offset] == '}' ) { + ++offset; return std::move( Object ); + } + + while( true ) { + JSON Key = parse_next( str, offset, on_error ); + consume_ws( str, offset ); + if( str[offset] != ':' ) { + on_error(std::string("Error: Object: Expected colon, found '") + str[offset] + "'"); + break; + } + consume_ws( str, ++offset ); + JSON Value = parse_next( str, offset, on_error ); + Object[Key.toString()] = Value; + + consume_ws( str, offset ); + if( str[offset] == ',' ) { + ++offset; continue; + } + else if( str[offset] == '}' ) { + ++offset; break; + } + else { + on_error(std::string("ERROR: Object: Expected comma, found '") + str[offset] + "'"); + break; + } + } + + return std::move( Object ); +} + +static JSON parse_array( const string &str, size_t &offset, std::function on_error ) { + JSON Array = JSON::Make( JSON::Class::Array ); + unsigned index = 0; + + ++offset; + consume_ws( str, offset ); + if( str[offset] == ']' ) { + ++offset; return std::move( Array ); + } + + while( true ) { + Array[index++] = parse_next( str, offset, on_error ); + consume_ws( str, offset ); + + if( str[offset] == ',' ) { + ++offset; continue; + } + else if( str[offset] == ']' ) { + ++offset; break; + } + else { + on_error(std::string("ERROR: Array: Expected ',' or ']', found '") + str[offset] + "'"); + return std::move( JSON::Make( JSON::Class::Array ) ); + } + } + + return std::move( Array ); +} + +static JSON parse_string( const string &str, size_t &offset, std::function on_error ) { + JSON String; + string val; + for( char c = str[++offset]; c != '\"' ; c = str[++offset] ) { + if( c == '\\' ) { + switch( str[ ++offset ] ) { + case '\"': val += '\"'; break; + case '\\': val += '\\'; break; + case '/' : val += '/' ; break; + case 'b' : val += '\b'; break; + case 'f' : val += '\f'; break; + case 'n' : val += '\n'; break; + case 'r' : val += '\r'; break; + case 't' : val += '\t'; break; + case 'u' : { + val += "\\u" ; + for( unsigned i = 1; i <= 4; ++i ) { + c = str[offset+i]; + if( (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') ) + val += c; + else { + on_error(std::string("ERROR: String: Expected hex character in unicode escape, found '") + c + "'"); + return std::move( JSON::Make( JSON::Class::String ) ); + } + } + offset += 4; + } break; + default : val += '\\'; break; + } + } + else + val += c; + } + ++offset; + String = val; + return std::move( String ); +} + +static JSON parse_number( const string &str, size_t &offset, std::function on_error ) { + JSON Number; + string val, exp_str; + char c; + bool isDouble = false; + long exp = 0; + while( true ) { + c = str[offset++]; + if( (c == '-') || (c >= '0' && c <= '9') ) + val += c; + else if( c == '.' ) { + val += c; + isDouble = true; + } + else + break; + } + if( c == 'E' || c == 'e' ) { + c = str[ offset++ ]; + if( c == '-' ){ ++offset; exp_str += '-';} + while( true ) { + c = str[ offset++ ]; + if( c >= '0' && c <= '9' ) + exp_str += c; + else if( !isspace( c ) && c != ',' && c != ']' && c != '}' ) { + on_error(std::string("ERROR: Number: Expected a number for exponent, found '") + c + "'"); + return std::move( JSON::Make( JSON::Class::Null ) ); + } + else + break; + } + exp = std::stol( exp_str ); + } + else if( !isspace( c ) && c != ',' && c != ']' && c != '}' ) { + on_error(std::string("ERROR: Number: unexpected character '") + c + "'"); + return std::move( JSON::Make( JSON::Class::Null ) ); + } + --offset; + + if( isDouble ) + Number = std::stod( val ) * std::pow( 10, exp ); + else { + if( !exp_str.empty() ) + Number = std::stol( val ) * std::pow( 10, exp ); + else + Number = std::stol( val ); + } + return std::move( Number ); +} + +static JSON parse_bool( const string &str, size_t &offset, std::function on_error ) { + JSON Bool; + if( str.substr( offset, 4 ) == "true" ) + Bool = true; + else if( str.substr( offset, 5 ) == "false" ) + Bool = false; + else { + on_error(std::string("ERROR: Bool: Expected 'true' or 'false', found '") + str.substr( offset, 5 ) + "'"); + return std::move( JSON::Make( JSON::Class::Null ) ); + } + offset += (Bool.toBool() ? 4 : 5); + return std::move( Bool ); +} + +static JSON parse_null( const string &str, size_t &offset, std::function on_error ) { + JSON Null; + if( str.substr( offset, 4 ) != "null" ) { + on_error(std::string("ERROR: Null: Expected 'null', found '") + str.substr( offset, 4 ) + "'" ); + return std::move( JSON::Make( JSON::Class::Null ) ); + } + offset += 4; + return std::move( Null ); +} + +static JSON parse_next( const string &str, size_t &offset, std::function on_error ) { + char value; + consume_ws( str, offset ); + value = str[offset]; + switch( value ) { + case '[' : return std::move( parse_array( str, offset, on_error ) ); + case '{' : return std::move( parse_object( str, offset, on_error ) ); + case '\"': return std::move( parse_string( str, offset, on_error ) ); + case 't' : + case 'f' : return std::move( parse_bool( str, offset, on_error ) ); + case 'n' : return std::move( parse_null( str, offset, on_error ) ); + default : if( ( value <= '9' && value >= '0' ) || value == '-' ) + return std::move( parse_number( str, offset, on_error ) ); + } + on_error(std::string("ERROR: Parse: Unknown starting character '") + value + "'"); + return JSON(); +} + +JSON JSON::Load( const string &str, std::function on_error) { + size_t offset = 0; + return std::move( parse_next( str, offset, on_error ) ); +} + diff --git a/rtkwebview/json.h b/rtkwebview/json.h new file mode 100644 index 0000000..87f1b57 --- /dev/null +++ b/rtkwebview/json.h @@ -0,0 +1,217 @@ +#ifndef JSON_H +#define JSON_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +std::string json_escape( const std::string &str ); + +class JSON +{ + union BackingData { + BackingData( double d ) : Float( d ){} + BackingData( long l ) : Int( l ){} + BackingData( bool b ) : Bool( b ){} + BackingData( std::string s ) : String( new std::string( s ) ){} + BackingData() : Int( 0 ){} + + std::deque *List; + std::map *Map; + std::string *String; + double Float; + long Int; + bool Bool; + } Internal; + +public: + enum class Class { + Null, + Object, + Array, + String, + Floating, + Integral, + Boolean + }; + + template + class JSONWrapper { + Container *object; + + public: + JSONWrapper( Container *val ) : object( val ) {} + JSONWrapper( std::nullptr_t ) : object( nullptr ) {} + + typename Container::iterator begin() { return object ? object->begin() : typename Container::iterator(); } + typename Container::iterator end() { return object ? object->end() : typename Container::iterator(); } + typename Container::const_iterator begin() const { return object ? object->begin() : typename Container::iterator(); } + typename Container::const_iterator end() const { return object ? object->end() : typename Container::iterator(); } + }; + + template + class JSONConstWrapper { + const Container *object; + + public: + JSONConstWrapper( const Container *val ) : object( val ) {} + JSONConstWrapper( std::nullptr_t ) : object( nullptr ) {} + + typename Container::const_iterator begin() const { return object ? object->begin() : typename Container::const_iterator(); } + typename Container::const_iterator end() const { return object ? object->end() : typename Container::const_iterator(); } + }; + + JSON(); + JSON( std::nullptr_t ) : Internal(), Type( Class::Null ){} + JSON( std::initializer_list list ); + JSON( JSON&& other ); + JSON( const JSON &other ); + + JSON& operator=( const JSON &other ); + JSON& operator=( JSON&& other ); + + // Template T constructors + template + JSON( T b, typename std::enable_if::value>::type* = 0 ); + + template + JSON( T i, typename std::enable_if::value && !std::is_same::value>::type* = 0 ); + + template + JSON( T f, typename std::enable_if::value>::type* = 0 ); + + template + JSON( T s, typename std::enable_if::value>::type* = 0 ); + + ~JSON() { + switch( Type ) { + case Class::Array: + delete Internal.List; + break; + case Class::Object: + delete Internal.Map; + break; + case Class::String: + delete Internal.String; + break; + default:; + } + } + + static JSON Make( Class type ); + static JSON Load( const std::string &, std::function ); + + // Appending things. + + template + void append( T arg ) { + SetType( Class::Array ); Internal.List->emplace_back( arg ); + } + + template + void append( T arg, U... args ) { + append( arg ); append( args... ); + } + + // Assignments (template T). + + template + typename std::enable_if::value, JSON&>::type operator=( T b ) { + SetType( Class::Boolean ); Internal.Bool = b; return *this; + } + + template + typename std::enable_if::value && !std::is_same::value, JSON&>::type operator=( T i ) { + SetType( Class::Integral ); Internal.Int = i; return *this; + } + + template + typename std::enable_if::value, JSON&>::type operator=( T f ) { + SetType( Class::Floating ); Internal.Float = f; return *this; + } + + template + typename std::enable_if::value, JSON&>::type operator=( T s ) { + SetType( Class::String ); *Internal.String = std::string( s ); return *this; + } + + + + // Indexing. + JSON& operator[]( const std::string &key ); + JSON& operator[]( unsigned index ); + + JSON &at( const std::string &key ); + const JSON &at( const std::string &key ) const; + JSON &at( unsigned index ); + const JSON &at( unsigned index ) const; + + int length() const; + int size() const; + + bool hasKey( const std::string &key ) const; + + Class JSONType() const; + + /// Functions for getting primitives from the JSON object. + bool IsNull() const; + + std::string toString() const; + std::string toString( bool &ok ) const; + + double toFloat() const; + double toFloat( bool &ok ) const; + + long toInt() const; + long toInt( bool &ok ) const; + + bool toBool() const; + bool toBool( bool &ok ) const; + + JSONWrapper> ObjectRange(); + JSONWrapper> ArrayRange(); + JSONConstWrapper> ObjectRange() const; + JSONConstWrapper> ArrayRange() const; + + std::string dump( int depth = 1, const std::string &tab = std::string(" ")) const; + + friend std::ostream& operator<<( std::ostream&, const JSON & ); + +private: + void SetType( Class type ); + +private: + /* beware: only call if YOU know that Internal is allocated. No checks performed here. + This function should be called in a constructed JSON just before you are going to + overwrite Internal... + */ + void ClearInternal(); + +private: + + Class Type = Class::Null; +}; + +JSON Array(); + +template +JSON Array( T... args ) { + JSON arr = JSON::Make( JSON::Class::Array ); + arr.append( args... ); + return std::move( arr ); +} + +JSON Object(); + +std::ostream& operator<<( std::ostream &os, const JSON &json ); + + +#endif // JSON_H diff --git a/rtkwebview/main.cpp b/rtkwebview/main.cpp new file mode 100644 index 0000000..7c6ba58 --- /dev/null +++ b/rtkwebview/main.cpp @@ -0,0 +1,25 @@ +#include "rktwebview.h" +#include + +int main() +{ + rkt_webview_t *wv = rkt_create_webview(); + result_t r = rkt_webview_navigate(wv, "https://wikipedia.org"); + //result_t r = rkt_set_html(wv, "He daar!

He daar!

"); + printf("Navigate: result = %d\n", r); + int i = 0; + while(rkt_webview_valid(wv)) { + printf("Waiting...\n"); + Sleep(1000); + i += 1; + if (i > 5) { + item_t item = rkt_webview_call_js(wv, "{ window.location = 'https://dijkewijk.nl'; return 42; }"); + printf("%d, %s\n", item.context, item.data); + rkt_webview_destroy_item(item); + item_t item1 = rkt_webview_call_js(wv, "{ return stuffwrong + 5 }"); + printf("%d, %s\n", item1.context, item1.data); + rkt_webview_destroy_item(item1); + } + } + return 0; +} diff --git a/rtkwebview/rktwebview.cpp b/rtkwebview/rktwebview.cpp new file mode 100644 index 0000000..6e127fe --- /dev/null +++ b/rtkwebview/rktwebview.cpp @@ -0,0 +1,514 @@ +#include "rktwebview.h" + +#include +#include "json.h" + +static void queue_init(queue_t *q); +static void enqueue(queue_t *q, item_t item); +static bool dequeue(queue_t *q, item_t *item); +static int queue_length(queue_t *q); +static void queue_destroy(queue_t *q); +static void free_item(item_t i); + +static void mutex_lock(rkt_webview_t *); +static void mutex_unlock(rkt_webview_t *); +static void thread_sleep_ms(rkt_webview_t *, int ms); + +static void handle_event(const char *id, const char *data, void *_wv); +static void handle_js_call(const char *id, const char *data, void *_wv); + +static void dispatcher(webview_t w, void *args); +static result_t do_dispatch(rkt_webview_t *, item_t item); + +#ifdef USE_WIN_THREADS +static DWORD webviewThread(LPVOID args) +#endif +#ifdef USE_PTHREADS +static int webviewThread(void *args) +#endif +{ + rkt_webview_t *wv = (rkt_webview_t *) args; + mutex_lock(wv); + webview_t w = webview_create(0, NULL); + wv->webview_handle = w; + wv->handle_set = true; + webview_bind(w, "web_ui_wire_handle_event", handle_event, wv); + webview_bind(w, "rkt_webview_call_js_result", handle_js_call, wv); + mutex_unlock(wv); + webview_run(w); + webview_destroy(w); + mutex_lock(wv); + wv->webview_handle = NULL; + wv->handle_destroyed = true; + mutex_unlock(wv); + return 0; +} + +#ifdef USE_WIN_THREADS +static DWORD queueGuardThread(LPVOID args) +#endif +#ifdef USE_PTHREADS + static int queueGuardThread(void *args) +#endif +{ + rkt_webview_t *wv = (rkt_webview_t *) args; + + auto handle_ok = [](rkt_webview_t *wv) { + mutex_lock(wv); + bool ok = !wv->handle_destroyed; + mutex_unlock(wv); + return ok; + }; + + while(handle_ok(wv)) { + mutex_lock(wv); + if (wv->queue_callback != NULL) { + while(queue_length(&wv->from_webview) > 0) { + item_t item; + dequeue(&wv->from_webview, &item); + wv->queue_callback(wv->queue_callback_id, item); + } + } + mutex_unlock(wv); + thread_sleep_ms(wv, 10); + } + + return 0; +} + + +RKTWEBVIEW_EXPORT rkt_webview_t *rkt_create_webview() +{ + rkt_webview_t *wv = (rkt_webview_t *) malloc(sizeof(rkt_webview_t)); + if (wv == NULL) { return NULL; } + + #ifdef _WIN32 + wv->mutex = CreateMutex(NULL, FALSE, NULL); + #endif + wv->handle_set = false; + wv->handle_destroyed = false; + queue_init(&wv->to_webview); + queue_init(&wv->from_webview); + wv->webview_handle = NULL; + wv->queue_callback = NULL; + wv->js_call_nr = 0; + wv->js_evaluated = new std::map(); + + #ifdef USE_WIN_THREADS + wv->webview_thread = CreateThread( + NULL, + 0, + webviewThread, + wv, + 0, + &wv->webview_thread_id + ); + wv->queue_guard_thread = CreateThread( + NULL, + 0, + queueGuardThread, + wv, + 0, + &wv->queue_guard_thread_id + ); + #endif + #ifdef USE_PTHREAD + #endif + bool go_on = true; + while(go_on) { + mutex_lock(wv); + go_on = (wv->handle_set == false); + mutex_unlock(wv); + if (go_on) { + thread_sleep_ms(wv, 10); + } + } + return wv; +} + +RKTWEBVIEW_EXPORT result_t rkt_webview_navigate(rkt_webview_t *wv, const char *url) +{ + item_t item = { CONTEXT_NAVIGATE, const_cast(url) }; + return do_dispatch(wv, item); +} + +RKTWEBVIEW_EXPORT result_t rkt_webview_set_html(rkt_webview_t *wv, const char *html) +{ + item_t item = { CONTEXT_SET_HTML, const_cast(html) }; + return do_dispatch(wv, item); +} + +RKTWEBVIEW_EXPORT result_t rkt_webview_run_js(rkt_webview_t *wv, const char *js) +{ + item_t item = { CONTEXT_EVAL_JS, const_cast(js) }; + return do_dispatch(wv, item); +} + +RKTWEBVIEW_EXPORT item_t rkt_webview_call_js(rkt_webview_t *wv, const char *js) +{ + mutex_lock(wv); + wv->js_call_nr += 1; + int call_nr = wv->js_call_nr; + mutex_unlock(wv); + char buf[30]; + sprintf(buf, "%d", call_nr); + std::string _js = std::string("{ let f = function() { ") + js + " };" + + " let call_nr = " + buf + ";" + + " try { let r = { result: f() };" + + " rkt_webview_call_js_result(call_nr, true, JSON.stringify(r)); " + + " } catch(e) {" + + " rkt_webview_call_js_result(call_nr, false, e.message); " + + " }" + + "}"; ""; + + + item_t item = { CONTEXT_CALL_JS, strdup(_js.c_str()) }; + result_t r = do_dispatch(wv, item); + if (r == error) { + item_t item = { CONTEXT_INVALID , NULL }; + return item; + } + + auto has_result = [](rkt_webview_t *wv, int call_nr) { + mutex_lock(wv); + bool result = false; + if (wv->js_evaluated->find(call_nr) != wv->js_evaluated->end()) { + mutex_unlock(wv); + return true; + } else { + mutex_unlock(wv); + return false; + } + }; + + while(!has_result(wv, call_nr)) { + thread_sleep_ms(wv, 2); + } + + free_item(item); + + mutex_lock(wv); + JSON j = JSON::Load(wv->js_evaluated->at(call_nr), [](std::string) { }); + bool result_oke = j[1].toBool(); + std::string data = j[2].toString(); + + item_t result_item; + if (result_oke) { + result_item = { CONTEXT_CALL_JS, strdup(data.c_str()) }; + } else { + result_item = { CONTEXT_INVALID, strdup(data.c_str()) }; + } + wv->js_evaluated->erase(call_nr); + mutex_unlock(wv); + + return result_item; +} + +RKTWEBVIEW_EXPORT bool rkt_webview_valid(rkt_webview_t *handle) +{ + if (handle == NULL) { + return false; + } else { + bool valid; + mutex_lock(handle); + valid = (!handle->handle_destroyed && handle->handle_set); + mutex_unlock(handle); + return valid; + } +} + +RKTWEBVIEW_EXPORT result_t rkt_destroy_webview(rkt_webview_t *wv) +{ + if (rkt_webview_valid(wv)) { + webview_error_t e = webview_terminate(WEBVIEW_HANDLE(wv)); + result_t r = (e >= 0) ? oke : error; + if (r == oke) { + queue_destroy(&wv->to_webview); + queue_destroy(&wv->from_webview); +#ifdef USE_WIN_THREADS + WaitForSingleObject(wv->webview_thread, 2000); // Give up after 2s. + CloseHandle(wv->webview_thread); + WaitForSingleObject(wv->queue_guard_thread, 2000); // Give up after 2s. + CloseHandle(wv->queue_guard_thread); + CloseHandle(wv->mutex); +#endif +#ifdef USE_PTHREAD +#endif + delete wv->js_evaluated; + free(wv); + } + return r; + } else { + return error; + } +} + +RKTWEBVIEW_EXPORT int rkt_webview_pending_events(rkt_webview_t *wv) +{ + mutex_lock(wv); + int len = queue_length(&wv->from_webview); + mutex_unlock(wv); + return len; +} + + +RKTWEBVIEW_EXPORT item_t rkt_webview_get_event(rkt_webview_t *wv) +{ + item_t i; + mutex_lock(wv); + if (queue_length(&wv->from_webview) > 0) { + dequeue(&wv->from_webview, &i); + } else { + i.context = CONTEXT_INVALID; + i.data = NULL; + } + mutex_unlock(wv); + return i; +} + + +RKTWEBVIEW_EXPORT void rkt_webview_destroy_item(item_t item) +{ + free_item(item); +} + + +RKTWEBVIEW_EXPORT result_t rkt_webview_devtools(rkt_webview_t *wv) +{ + item_t item = { CONTEXT_OPEN_DEVTOOLS, const_cast("") }; + return do_dispatch(wv, item); +} + +RKTWEBVIEW_EXPORT reason_t rkt_webview_last_reason(rkt_webview_t *wv) +{ + return wv->last_reason; +} + +RKTWEBVIEW_EXPORT void rkt_webview_register_queue_callback(rkt_webview_t *wv, int id, void (*cb)(int, item_t)) +{ + mutex_lock(wv); + wv->queue_callback = cb; + wv->queue_callback_id = id; + mutex_unlock(wv); +} + +static void handle_event(const char *id, const char *data, void *_wv) +{ + rkt_webview_t *wv = static_cast(_wv); + mutex_lock(wv); + + JSON json; + json["id"] = id; + json["data"] = data; + std::string s = json.dump(); + + item_t item; + item.context = CONTEXT_BOUND_EVENT; + item.data = const_cast(s.c_str()); + + enqueue(&wv->from_webview, item); + mutex_unlock(wv); +} + +static void handle_js_call(const char *id, const char *data, void *_wv) +{ + rkt_webview_t *wv = static_cast(_wv); + mutex_lock(wv); + std::string d(data); + JSON j = JSON::Load(d, [](std::string err) { }); + int call_nr = j[0].toInt(); + wv->js_evaluated->insert(std::pair(call_nr, std::string(data))); + mutex_unlock(wv); +} + +void dispatcher(webview_t w, void *args) +{ + rkt_webview_t *wv = reinterpret_cast(args); + item_t item; + mutex_lock(wv); + if (dequeue(&wv->to_webview, &item)) { + if (item.context == CONTEXT_SET_HTML) { + webview_error_t e = webview_set_html(w, item.data); + wv->wv_res = static_cast(e); + wv->last_result = (e >= 0) ? oke : error; + wv->last_reason = (wv->last_result == oke) ? reason_oke : reason_set_html_failed; + } else if (item.context == CONTEXT_NAVIGATE) { + webview_error_t e = webview_navigate(w, item.data); + wv->wv_res = static_cast(e); + wv->last_result = (e >= 0) ? oke : error; + wv->last_reason = (wv->last_result == oke) ? reason_oke : reason_set_navigate_failed; + } else if (item.context == CONTEXT_EVAL_JS || item.context == CONTEXT_CALL_JS) { + webview_error_t e = webview_eval(w, item.data); + wv->wv_res = static_cast(e); + wv->last_result = (e >= 0) ? oke : error; + wv->last_reason = (wv->last_result == oke) ? reason_oke : reason_eval_js_failed; + } else if (item.context == CONTEXT_OPEN_DEVTOOLS) { + bool handled = false; +#ifdef _WIN32 + void *handle = webview_get_native_handle(wv->webview_handle, WEBVIEW_NATIVE_HANDLE_KIND_BROWSER_CONTROLLER); + ICoreWebView2Controller *c = static_cast(handle); + ICoreWebView2 *cwv = nullptr; + HRESULT r = c->get_CoreWebView2(&cwv); + if (cwv != nullptr) { + r = cwv->OpenDevToolsWindow(); + if (r == S_OK) { + wv->last_result = oke; + wv->last_reason = reason_oke; + } else { + wv->last_result = error; + wv->last_reason = reason_no_devtools_on_platform; + } + } else { + wv->last_result = error; + wv->last_reason = reason_no_devtools_on_platform; + } +#endif + if (!handled) { + wv->last_result = error; + wv->last_reason = reason_no_devtools_on_platform; + } + } else { + wv->last_result = error; + wv->last_reason = reason_no_delegate_for_context; + } + } + mutex_unlock(wv); + free_item(item); +} + +result_t do_dispatch(rkt_webview_t *wv, item_t item) +{ + webview_t w = WEBVIEW_HANDLE(wv); + + mutex_lock(wv); + enqueue(&wv->to_webview, item); + wv->last_reason = reason_no_result_yet; + mutex_unlock(wv); + + webview_error_t e = webview_dispatch(w, dispatcher, wv); + + reason_t lr; + result_t r; + + if (e >= 0) { + bool go_on = true; + while(go_on) { + mutex_lock(wv); + r = wv->last_result; + lr = wv->last_reason; + mutex_unlock(wv); + if (lr != reason_no_result_yet) { + go_on = false; + } else { + thread_sleep_ms(wv, 10); + } + } + return r; + } else { + lr = reason_set_html_failed; + r = error; + } + + printf("error = %d, reason = %d", r, lr); + + return r; +} + +///////////////////////////////////////////////////////////////////// +// Supporting functions +///////////////////////////////////////////////////////////////////// + +void queue_init(queue_t *q) +{ + q->length = 0; + q->first = NULL; + q->last = NULL; +} + +void enqueue(queue_t *q, item_t item) +{ + queue_item_t *itm = (queue_item_t *) malloc(sizeof(queue_item_t)); + itm->item.context = item.context; + itm->item.data = strdup(item.data); + if (q->first == NULL) { + q->first = itm; + q->last = itm; + itm->prev = NULL; + itm->next = NULL; + q->length = 1; + } else { + itm->prev = q->last; + itm->next = NULL; + q->last->next = itm; + q->last = itm; + q->length += 1; + } +} + +bool dequeue(queue_t *q, item_t *item) +{ + if (q->length == 0) { + item->context = CONTEXT_INVALID; + item->data = NULL; + return false; + } else { + queue_item_t *itm = q->first; + q->first = q->first->next; + q->length -= 1; + if (q->length == 0) { + q->first = NULL; + q->last = NULL; + } + item->context = itm->item.context; + item->data = itm->item.data; + free(itm); + return true; + } +} + +int queue_length(queue_t *q) +{ + return q->length; +} + +void queue_destroy(queue_t *q) +{ + item_t i; + while(dequeue(q, &i)) { + free(i.data); + } +} + +void free_item(item_t item) +{ + free(item.data); +} + + +void mutex_lock(rkt_webview_t *wv) +{ +#ifdef USE_WIN_THREADS + WaitForSingleObject(wv->mutex, INFINITE); +#endif +#ifdef USE_PTHREAD +#endif +} + + +void mutex_unlock(rkt_webview_t *wv) +{ +#ifdef USE_WIN_THREADS + ReleaseMutex(wv->mutex); +#endif +#ifdef USE_PTHREAD +#endif +} + +static void thread_sleep_ms(rkt_webview_t *wv, int ms) +{ +#ifdef USE_WIN_THREADS + Sleep(ms); +#endif +#ifdef USE_PTHREAD +#endif +} diff --git a/rtkwebview/rktwebview.h b/rtkwebview/rktwebview.h new file mode 100644 index 0000000..939cefc --- /dev/null +++ b/rtkwebview/rktwebview.h @@ -0,0 +1,121 @@ +#ifndef RKTWEBVIEW_H +#define RKTWEBVIEW_H + +#include "rktwebview_global.h" +#include +#include + +#ifdef _WIN32 +#include +typedef HANDLE mutex_t; +typedef HANDLE thread_t; +typedef DWORD thread_id_t; +#define USE_WIN_THREADS +#endif + +#ifdef _linux +#define USE_PTHREADS +#include +#endif + +#define CONTEXT_INVALID 0 +#define CONTEXT_BOUND_EVENT 1 +#define CONTEXT_WINDOW_RESIZE 2 +#define CONTEXT_WINDOW_MOVE 3 +#define CONTEXT_WINDOW_CAN_CLOSE 4 +#define CONTEXT_WINDOW_CLOSED 5 +#define CONTEXT_SET_HTML 6 +#define CONTEXT_NAVIGATE 7 +#define CONTEXT_EVAL_JS 8 +#define CONTEXT_OPEN_DEVTOOLS 9 +#define CONTEXT_CALL_JS 10 + +typedef enum { + oke = 0, + error = 1 +} result_t; + +typedef enum { + reason_no_result_yet = -1, + reason_oke = 0, + reason_set_html_failed, + reason_set_navigate_failed, + reason_eval_js_failed, + reason_no_devtools_on_platform, + reason_no_delegate_for_context +} reason_t; + +typedef struct { + int context; + char *data; +} item_t; + +typedef struct _item { + item_t item; + struct _item *next; + struct _item *prev; +} queue_item_t; + +typedef struct { + queue_item_t *first; + queue_item_t *last; + int length; +} queue_t; + +typedef struct { + void *webview_handle; + mutex_t mutex; + thread_t webview_thread; + thread_id_t webview_thread_id; + queue_t to_webview; + queue_t from_webview; + int wv_res; + result_t last_result; + reason_t last_reason; + bool handle_set; + bool handle_destroyed; + void (*queue_callback)(int id, item_t data); + int queue_callback_id; + thread_t queue_guard_thread; + thread_id_t queue_guard_thread_id; + std::map *js_evaluated; + int js_call_nr; +} rkt_webview_t; + +#define WEBVIEW_HANDLE(wv) reinterpret_cast(wv->webview_handle) + +extern "C" { + +RKTWEBVIEW_EXPORT rkt_webview_t *rkt_create_webview(); +RKTWEBVIEW_EXPORT result_t rkt_destroy_webview(rkt_webview_t *wv); + +RKTWEBVIEW_EXPORT result_t rkt_close_webview(rkt_webview_t *handle); +RKTWEBVIEW_EXPORT result_t rkt_webview_navigate(rkt_webview_t *handle, const char *url); +RKTWEBVIEW_EXPORT result_t rkt_webview_set_html(rkt_webview_t *handle, const char *html); + + +RKTWEBVIEW_EXPORT result_t rkt_webview_run_js(rkt_webview_t *handle, const char *js); +RKTWEBVIEW_EXPORT item_t rkt_webview_call_js(rkt_webview_t *handle, const char *js); + +//RKTWEBVIEW_EXPORT result_t rkt_bind(rkt_webview_t *handle, const char *selector, const char *event); +//RKTWEBVIEW_EXPORT result_t rkt_unbind(rkt_webview_t *handle, const char *selector, const char *event); + +RKTWEBVIEW_EXPORT bool rkt_webview_valid(rkt_webview_t *handle); + +RKTWEBVIEW_EXPORT int rkt_webview_pending_events(rkt_webview_t *wv); +RKTWEBVIEW_EXPORT item_t rkt_webview_get_event(rkt_webview_t *wv); +RKTWEBVIEW_EXPORT void rkt_webview_register_queue_callback(rkt_webview_t *wv, int id, void(*cb)(int id, item_t item)); +RKTWEBVIEW_EXPORT void rkt_webview_destroy_item(item_t item); + +RKTWEBVIEW_EXPORT result_t rkt_webview_move(rkt_webview_t *wv, int x, int y); +RKTWEBVIEW_EXPORT result_t rkt_webview_resize(rkt_webview_t *wv, int w, int h); +RKTWEBVIEW_EXPORT result_t rkt_webview_set_title(rkt_webview_t *wv, const char *title); + +RKTWEBVIEW_EXPORT result_t rkt_webview_devtools(rkt_webview_t *wv); + +RKTWEBVIEW_EXPORT reason_t rkt_webview_last_reason(rkt_webview_t *wv); + + +} + +#endif // RKTWEBVIEW_H diff --git a/rtkwebview/rktwebview_global.h b/rtkwebview/rktwebview_global.h new file mode 100644 index 0000000..9d04e28 --- /dev/null +++ b/rtkwebview/rktwebview_global.h @@ -0,0 +1,19 @@ +#ifndef RKTWEBVIEW_GLOBAL_H +#define RKTWEBVIEW_GLOBAL_H + +#if defined(_MSC_VER) || defined(WIN64) || defined(_WIN64) || defined(__WIN64__) || defined(WIN32) \ + || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) +#define Q_DECL_EXPORT __declspec(dllexport) +#define Q_DECL_IMPORT __declspec(dllimport) +#else +#define Q_DECL_EXPORT __attribute__((visibility("default"))) +#define Q_DECL_IMPORT __attribute__((visibility("default"))) +#endif + +#if defined(RKTWEBVIEW_LIBRARY) +#define RKTWEBVIEW_EXPORT Q_DECL_EXPORT +#else +#define RKTWEBVIEW_EXPORT Q_DECL_IMPORT +#endif + +#endif // RKTWEBVIEW_GLOBAL_H diff --git a/utils.rkt b/utils.rkt new file mode 100644 index 0000000..00679f5 --- /dev/null +++ b/utils.rkt @@ -0,0 +1,22 @@ +#lang racket/base + +(provide while) + +(define-syntax while + (syntax-rules () + ((_ cond + body ...) + (letrec ((while (λ () + (if cond + (begin + (displayln "cond = true") + (begin body ...) + (while)) + (begin + (displayln "cond = false") + 'done))) + )) + (while))) + ) + ) + \ No newline at end of file