| /******************************************************************************* |
| * Copyright (c) 2017 Red Hat and others. All rights reserved. |
| * The contents of this file are made available under the terms |
| * of the GNU Lesser General Public License (LGPL) Version 2.1 that |
| * accompanies this distribution (lgpl-v21.txt). The LGPL is also |
| * available at http://www.gnu.org/licenses/lgpl.html. If the version |
| * of the LGPL at http://www.gnu.org is different to the version of |
| * the LGPL accompanying this distribution and there is any conflict |
| * between the two license versions, the terms of the LGPL accompanying |
| * this distribution shall govern. |
| * |
| * Contributors: |
| * Red Hat - initial API and implementation |
| *******************************************************************************/ |
| |
| package org.eclipse.swt.browser; |
| |
| import java.util.*; |
| |
| import org.eclipse.swt.*; |
| import org.eclipse.swt.internal.*; |
| import org.eclipse.swt.internal.gtk.*; |
| import org.eclipse.swt.widgets.*; |
| |
| /** |
| * Logic for Webkit to interact with it's Webkit extension via GDBus. |
| * |
| * While this class supports quite a bit of GDBus and gvariant support, it is by no means a complete |
| * implementation and it's tailored to support Java to Javascript conversion. (E.g all Numbers are converted to Double). |
| * If this is ever to be used outside of Webkit, then care must be taken to deal with |
| * cases that are not currently implemented/used. See: WebKitGTK.java 'TYPE NOTES' |
| * |
| * For hygiene purposes, GVariant types should not be leaving this class. Convert on the way in/out. |
| * |
| * @category gdbus |
| */ |
| class WebkitGDBus { |
| /* |
| * If any of the GDBus names/paths/interfaces need to be changed, then they also need to be changed |
| * in the extension (webkitgtk_extension.c). |
| */ |
| |
| /* WEBKITGDBUS_DBUS_NAME isn't actually used but is here for informative purposes */ |
| @SuppressWarnings("unused") |
| private static final String WEBKITGDBUS_DBUS_NAME = "org.eclipse.swt"; |
| private static final byte [] WEBKITGDBUS_OBJECT_PATH = Converter.javaStringToCString("/org/eclipse/swt/gdbus"); |
| private static final String WEBKITGDBUS_INTERFACE_NAME_JAVA = "org.eclipse.swt.gdbusInterface"; |
| private static final byte [] WEBKITGDBUS_INTERFACE_NAME = Converter.javaStringToCString("org.eclipse.swt.gdbusInterface"); |
| |
| /* Extension connection details, in byte [] form */ |
| private static final byte [] EXTENSION_DBUS_NAME = Converter.javaStringToCString("org.eclipse.swt.webkitgtk_extension"); |
| private static final byte [] EXTENSION_OBJECT_PATH = Converter.javaStringToCString("/org/eclipse/swt/webkitgtk_extension/gdbus"); |
| private static final byte [] EXTENSION_INTERFACE_NAME = Converter.javaStringToCString("org.eclipse.swt.webkitgtk_extension.gdbusInterface"); |
| |
| /** Extension GDBusServer client address */ |
| private static String EXTENSION_DBUS_SERVER_CLIENT_ADDRESS; |
| |
| /* Accepted GDBus methods */ |
| private static final String webkit2callJava = WebKit.WebKitExtension.getJavaScriptFunctionName(); |
| private static final String webkitWebExtensionIdentifier = WebKit.WebKitExtension.getWebExtensionIdentifier(); |
| |
| /* Connections */ |
| /** GDBusConnection from the web extension */ |
| static long connectionFromExtension; |
| /** GDBusConnection to the web extension */ |
| static long connectionToExtension; |
| /** A field that is set to true if the proxy connection has been established, false otherwise. */ |
| static boolean connectionToExtensionCreated; |
| |
| /* Server related objects */ |
| /** GDBusServer for the main SWT process */ |
| private static long gDBusServer = 0; |
| /** GDBusAuthObserver for the server */ |
| private static long authObserver = 0; |
| /** GUID of the GDBusServer */ |
| private static long guid = 0; |
| |
| /** Display this GDBus class is "attached" to */ |
| static Display display; |
| |
| |
| // BrowserFunction logic |
| /** Set to true if there are <code>BrowserFunction</code> objects waiting to be registered with the web extension.*/ |
| static boolean functionsPending; |
| /** |
| * HashMap that stores any BrowserFunctions which have been created but not yet registered with the web extension. |
| * These functions will be registered with the web extension as soon as the proxy to the extension is set up. |
| * |
| * The format of the HashMap is (page ID, list of function string and URL). |
| */ |
| static HashMap<Long, ArrayList<ArrayList<String>>> pendingBrowserFunctions = new HashMap<>(); |
| |
| |
| /** |
| * Interface is read/parsed at run time. No compilation with gdbus-code-gen necessary. |
| * |
| * Note, |
| * - When calling a method via g_dbus_proxy_call_sync(..g_variant params..), |
| * the g_variant that describes parameters should only mirror incoming parameters. |
| * Each type is a separate argument. |
| * e.g: |
| * g_variant xml: |
| * "(si)", "string", 42 .. arg type='s' |
| * .. arg type='i' |
| * |
| * - Nested parameters need to have a 2nd bracket around them. |
| * e.g: |
| * g_variant xml: |
| * "((r)i)", *gvariant, 42 .. arg type='r' |
| * .. arg type='i' |
| * |
| * - '@' is a pointer to a gvariant. so '@r' is a pointer to nested type, i.e *gvariant |
| * |
| * To understand the mappings, it's good to understand DBus and GVariant's syntax: |
| * https://dbus.freedesktop.org/doc/dbus-specification.html#idm423 |
| * https://developer.gnome.org/glib/stable/glib-GVariantType.html |
| * |
| * Be mindful about only using supported DBUS_TYPE_* , as convert* methods might fail otherwise. |
| * Alternatively, modify convert* methods. |
| */ |
| private static final byte [] WEBKITGDBUS_INTROSPECTION_XML = Converter.javaStringToCString( |
| "<node>" |
| + " <interface name='" + WEBKITGDBUS_INTERFACE_NAME_JAVA + "'>" |
| + " <method name='" + webkit2callJava + "'>" |
| + " <arg type='"+ OS.DBUS_TYPE_STRING + "' name='webViewPtr' direction='in'/>" |
| + " <arg type='"+ OS.DBUS_TYPE_DOUBLE + "' name='index' direction='in'/>" |
| + " <arg type='"+ OS.DBUS_TYPE_STRING + "' name='token' direction='in'/>" |
| + " <arg type='" + OS.DBUS_TYPE_SINGLE_COMPLETE + "' name='arguments' direction='in'/>" |
| + " <arg type='" + OS.DBUS_TYPE_SINGLE_COMPLETE + "' name='result' direction='out'/>" |
| + " </method>" |
| + " <method name='" + webkitWebExtensionIdentifier + "'>" |
| + " <arg type='"+ OS.DBUS_TYPE_STRING + "' name='webExtensionServerAddress' direction='in'/>" |
| + " <arg type='"+ OS.DBUS_TYPE_STRUCT_ARRAY_BROWSER_FUNCS + "' name='result' direction='out'/>" |
| + " </method>" |
| + " </interface>" |
| + "</node>"); |
| |
| /** |
| * GDBus/DBus doesn't have a notion of Null. |
| * To get around this, we use magic numbers to represent special cases. |
| * Currently this is specific to Webkit to deal with Javascript data type conversions. |
| * @category gdbus */ |
| private static final byte SWT_DBUS_MAGIC_NUMBER_EMPTY_ARRAY = 101; |
| /** @category gdbus */ |
| private static final byte SWT_DBUS_MAGIC_NUMBER_NULL = 48; |
| |
| |
| /** Callback for GDBus method handling */ |
| private static Callback handleMethodCB; |
| /** Callback for incoming connections to WebkitGDBus' server */ |
| private static Callback newConnectionCB; |
| /** Callback for creating a new connection to the web extension */ |
| private static Callback newConnectionToExtensionCB; |
| /** Callback for authenticating connections to WebkitGDBus' server */ |
| private static Callback authenticatePeerCB; |
| /** Callback for asynchronous proxy calls to the extension */ |
| private static Callback callExtensionAsyncCB; |
| |
| static { |
| handleMethodCB = new Callback (WebkitGDBus.class, "handleMethodCB", 8); //$NON-NLS-1$ |
| callExtensionAsyncCB = new Callback (WebkitGDBus.class, "callExtensionAsyncCB", 3); //$NON-NLS-1$ |
| newConnectionCB = new Callback (WebkitGDBus.class, "newConnectionCB", 3); //$NON-NLS-1$ |
| newConnectionToExtensionCB = new Callback (WebkitGDBus.class, "newConnectionToExtensionCB", 3); //$NON-NLS-1$ |
| authenticatePeerCB = new Callback (WebkitGDBus.class, "authenticatePeerCB", 4); //$NON-NLS-1$ |
| } |
| |
| /** True iff the GDBusServer has been initialized */ |
| static boolean initialized; |
| /** True iff this class has been attached to a Display */ |
| static boolean attachedToDisplay; |
| |
| /** This method is in an internal class and is not intended to be referenced by clients. */ |
| static long init () { |
| if (initialized) { |
| return gDBusServer; |
| } |
| initialized = true; |
| |
| // Generate address and GUID |
| long address = construct_server_address(); |
| authObserver = OS.g_dbus_auth_observer_new(); |
| guid = OS.g_dbus_generate_guid(); |
| |
| // Create server |
| long [] error = new long [1]; |
| gDBusServer = OS.g_dbus_server_new_sync(address, OS.G_DBUS_SERVER_FLAGS_NONE, guid, authObserver, 0, error); |
| |
| // Connect authentication and incoming connections signals to the newly created server, and start it |
| if (gDBusServer != 0) { |
| OS.g_signal_connect(gDBusServer, OS.new_connection, newConnectionCB.getAddress(), 0); |
| OS.g_signal_connect(authObserver, OS.authorize_authenticated_peer, authenticatePeerCB.getAddress(), 0); |
| OS.g_dbus_server_start(gDBusServer); |
| } else { |
| System.err.println("SWT WebKitGDBus: error creating DBus server " + Display.extractFreeGError(error[0])); |
| } |
| return gDBusServer; |
| } |
| |
| /** |
| * Sets the Display for this class. This allows WebkitGDBus to attach its servers and related objects |
| * to the provided Display. When the Display is disposed, it will will release the GDBus related |
| * resources. |
| * |
| * @param displayToSet the Display to set |
| */ |
| static void setDisplay (Display displayToSet) { |
| if (attachedToDisplay) { |
| return; |
| } else { |
| display = displayToSet; |
| |
| /* |
| * Add the GDBusServer, GDBusAuthObserver, and GUID to the Display. |
| * Note that we don't add the connections yet, because they likely |
| * don't exist. Those are added in callbacks as they come in. |
| */ |
| if (gDBusServer != 0) display.dBusServers.add(gDBusServer); |
| if (authObserver != 0) display.dBusAuthObservers.add(authObserver); |
| if (guid != 0) display.dBusGUIDS.add(guid); |
| attachedToDisplay = true; |
| } |
| } |
| |
| /** |
| * Constructs an address at which the GDBus server for the SWT main process |
| * can be reached. |
| * |
| * @return a pointer to the address |
| */ |
| private static long construct_server_address () { |
| // On Linux, the address required is used for as an abstract path. If abstract path is not supported |
| // one must create & manage temporary directories. See Bug562443. |
| byte [] address = Converter.wcsToMbcs("unix:tmpdir=/tmp/SWT-GDBusServer", true); |
| long addressPtr = OS.g_malloc(address.length); |
| C.memmove(addressPtr, address, address.length); |
| |
| return addressPtr; |
| } |
| |
| /** |
| * This is called when a client call one of the GDBus methods. |
| * |
| * Developer note: |
| * This method can be reached directly from GDBus cmd utility: |
| * gdbus call --session --dest org.eclipse.swt<UNIQUE_ID> --object-path /org/eclipse/swt/gdbus --method org.eclipse.swt.gdbusInterface.HelloWorld |
| * where as you tab complete, you append the UNIQUE_ID. |
| * |
| * @param connection GDBusConnection |
| * @param sender const gchar |
| * @param object_path const gchar |
| * @param interface_name const gchar |
| * @param method_name const gchar |
| * @param gvar_parameters GVariant |
| * @param invocation GDBusMethodInvocation |
| * @param user_data gpointer |
| * @return |
| */ |
| @SuppressWarnings("unused") // callback not directly called by SWT |
| private static long handleMethodCB ( |
| long connection, long sender, |
| long object_path, long interface_name, |
| long method_name, long gvar_parameters, |
| long invocation, long user_data) { |
| |
| String java_method_name = Converter.cCharPtrToJavaString(method_name, false); |
| Object result = null; |
| if (java_method_name != null) { |
| if (java_method_name.equals(webkit2callJava)) { |
| try { |
| Object [] java_parameters = (Object []) convertGVariantToJava(gvar_parameters); |
| result = WebKit.WebKitExtension.webkit2callJavaCallback(java_parameters); |
| } catch (Exception e) { |
| // gdbus should always return to prevent extension from hanging. |
| result = (String) WebBrowser.CreateErrorString (e.getLocalizedMessage ()); |
| System.err.println("SWT WebkitGDBus: Exception occured in Webkit2 callback logic."); |
| } |
| } else if (java_method_name.equals(webkitWebExtensionIdentifier)) { |
| Object [] serverAddress = (Object []) convertGVariantToJava(gvar_parameters); |
| if (serverAddress[0] != null && serverAddress[0] instanceof String) { |
| EXTENSION_DBUS_SERVER_CLIENT_ADDRESS = (String) serverAddress[0]; |
| // Connect to the extension's server by creating a connection asynchronously |
| createConnectionToExtension(); |
| /* |
| * Return any pending BrowserFunctions that were created before WebkitGDBus |
| * was initialized. |
| */ |
| invokeReturnValueExtensionIdentifier(pendingBrowserFunctions, invocation); |
| } else { |
| System.err.println("SWT WebkitGDBus: error in web extension identification process." |
| + " BrowserFunction may not work."); |
| } |
| return 0; |
| } |
| } else { |
| result = (String) "SWT WebkitGDBus: GDBus called an unknown method?"; |
| System.err.println("SWT WebkitGDBus: Received a call from an unknown method: " + java_method_name); |
| } |
| invokeReturnValue(result, invocation); |
| return 0; |
| } |
| |
| @SuppressWarnings("unused") // callback not directly called by SWT |
| private static long callExtensionAsyncCB (long source_object, long result, long user_data) { |
| long [] error = new long [1]; |
| long gVariantResult = OS.g_dbus_connection_call_finish (connectionToExtension, result, error); |
| if (error[0] != 0) { |
| String msg = Display.extractFreeGError(error[0]); |
| System.err.println("SWT WebkitGDBus: there was an error executing something asynchronously with the extension (Java callback)."); |
| System.err.println("SWT WebkitGDBus: the error message provided is " + msg); |
| } |
| OS.g_variant_unref(gVariantResult); |
| return 0; |
| } |
| |
| @SuppressWarnings("unused") // callback not directly called by SWT |
| private static long authenticatePeerCB (long observer, long stream, long credentials, long user_data) { |
| boolean authorized = false; |
| if (credentials != 0) { |
| long error [] = new long [1]; |
| long ownCredentials = OS.g_credentials_new(); |
| authorized = OS.g_credentials_is_same_user(credentials, ownCredentials, error); |
| if (error[0] != 0) { |
| String msg = Display.extractFreeGError(error[0]); |
| System.err.println("SWT WebkitGDBus: error authenticating client connection to server " + msg); |
| } |
| OS.g_object_unref(ownCredentials); |
| } |
| return authorized ? 1 : 0; |
| } |
| |
| @SuppressWarnings("unused") // callback not directly called by SWT |
| private static long newConnectionCB (long server, long connection, long user_data) { |
| long gdBusNodeInfo; |
| long [] error = new long [1]; |
| gdBusNodeInfo = OS.g_dbus_node_info_new_for_xml(WEBKITGDBUS_INTROSPECTION_XML, error); |
| if (gdBusNodeInfo == 0 || error[0] != 0) { |
| System.err.println("SWT WebkitGDBus: failed to get introspection data"); |
| } |
| assert gdBusNodeInfo != 0 : "SWT WebKitGDBus: introspection data should not be 0"; |
| |
| long interface_info = OS.g_dbus_node_info_lookup_interface(gdBusNodeInfo, WEBKITGDBUS_INTERFACE_NAME); |
| long vtable [] = { handleMethodCB.getAddress(), 0, 0 }; |
| |
| OS.g_dbus_connection_register_object( |
| connection, |
| WEBKITGDBUS_OBJECT_PATH, |
| interface_info, |
| vtable, |
| 0, // user_data |
| 0, // user_data_free_func |
| error); |
| |
| if (error[0] != 0) { |
| System.err.println("SWT WebKitGDBus: failed to register object: " + WEBKITGDBUS_OBJECT_PATH); |
| return 0; |
| } |
| |
| // Ref the connection and add it to the Display's list of connections |
| connectionFromExtension = OS.g_object_ref(connection); |
| if (attachedToDisplay && display != null) { |
| if (!display.dBusConnections.contains(connection)) display.dBusConnections.add(connectionFromExtension); |
| } |
| return 1; |
| } |
| |
| @SuppressWarnings("unused") // callback not directly called by SWT |
| private static long newConnectionToExtensionCB (long sourceObject, long result, long user_data) { |
| long [] error = new long [1]; |
| connectionToExtension = OS.g_dbus_connection_new_for_address_finish(result, error); |
| if (error[0] != 0) { |
| System.err.println("SWT WebKitGDBus: error finishing connection: " + Display.extractFreeGError(error[0])); |
| return 0; |
| } else { |
| connectionToExtensionCreated = true; |
| } |
| |
| // Add the connections to the Display's list of connections |
| if (attachedToDisplay && display != null) { |
| if (!display.dBusConnections.contains(connectionToExtension)) display.dBusConnections.add(connectionToExtension); |
| } |
| return 0; |
| } |
| |
| /** |
| * Returns a GVariant to the DBus invocation of the extension identifier method. When the extension |
| * is initialized it sends a DBus message to the SWT webkit instance. As a return value, the SWT webkit |
| * instance sends any BrowserFunctions that have been registered. If no functions have been registered, |
| * an "empty" function with a page ID of -1 is sent. |
| * |
| * @param map the HashMap of BrowserFunctions waiting to be registered in the extension, or null |
| * if you'd like to explicitly send an empty function signature |
| * @param invocation the GDBus invocation to return the value on |
| */ |
| private static void invokeReturnValueExtensionIdentifier (HashMap<Long, ArrayList<ArrayList<String>>> map, |
| long invocation) { |
| long resultGVariant; |
| long builder; |
| long type = OS.g_variant_type_new(OS.G_VARIANT_TYPE_ARRAY_BROWSER_FUNCS); |
| builder = OS.g_variant_builder_new(type); |
| if (builder == 0) return; |
| Object [] tupleArray = new Object[3]; |
| boolean sendEmptyFunction; |
| if (map == null) { |
| sendEmptyFunction = true; |
| } else { |
| sendEmptyFunction = map.isEmpty() && !functionsPending; |
| } |
| /* |
| * No functions to register, send a page ID of -1 and empty strings. |
| */ |
| if (sendEmptyFunction) { |
| tupleArray[0] = (long)-1; |
| tupleArray[1] = ""; |
| tupleArray[2] = ""; |
| long tupleGVariant = convertJavaToGVariant(tupleArray); |
| if (tupleGVariant != 0) { |
| OS.g_variant_builder_add_value(builder, tupleGVariant); |
| } else { |
| System.err.println("SWT WebKitGDBus: error creating empty BrowserFunction GVariant tuple, skipping."); |
| } |
| } else { |
| for (long id : map.keySet()) { |
| ArrayList<ArrayList<String>> list = map.get(id); |
| if (list != null) { |
| for (ArrayList<String> stringList : list) { |
| Object [] stringArray = stringList.toArray(); |
| if (stringArray.length > 2) { |
| System.err.println("SWT WebKitGDBus: String array with BrowserFunction and URL should never have" |
| + "more than 2 Strings"); |
| } |
| tupleArray[0] = id; |
| System.arraycopy(stringArray, 0, tupleArray, 1, 2); |
| long tupleGVariant = convertJavaToGVariant(tupleArray); |
| if (tupleGVariant != 0) { |
| OS.g_variant_builder_add_value(builder, tupleGVariant); |
| } else { |
| System.err.println("SWT WebKitGDBus: error creating BrowserFunction GVariant tuple, skipping."); |
| } |
| } |
| } |
| } |
| } |
| resultGVariant = OS.g_variant_builder_end(builder); |
| String typeString = Converter.cCharPtrToJavaString(OS.g_variant_get_type_string(resultGVariant), false); |
| if (!OS.DBUS_TYPE_STRUCT_ARRAY_BROWSER_FUNCS.equals(typeString)) { |
| System.err.println("SWT WebKitGDBus: an error packaging the GVariant occurred: type mismatch."); |
| } |
| long [] variants = {resultGVariant}; |
| long finalGVariant = OS.g_variant_new_tuple(variants, 1); |
| OS.g_dbus_method_invocation_return_value(invocation, finalGVariant); |
| OS.g_variant_builder_unref(builder); |
| OS.g_variant_type_free(type); |
| return; |
| } |
| |
| private static void invokeReturnValue (Object result, long invocation) { |
| long resultGVariant = 0; |
| try { |
| resultGVariant = convertJavaToGVariant(new Object [] {result}); // Result has to be a tuple. |
| } catch (SWTException e) { |
| // gdbus should always return to prevent extension from hanging. |
| String errMsg = (String) WebBrowser.CreateErrorString (e.getLocalizedMessage ()); |
| resultGVariant = convertJavaToGVariant(new Object [] {errMsg}); |
| } |
| OS.g_dbus_method_invocation_return_value(invocation, resultGVariant); |
| return; // void return value. |
| } |
| |
| /** |
| * Asynchronously initializes a GDBusConnection to the web extension. Connection process |
| * will be confirmed when the newConnectionToExtension callback is called. |
| */ |
| private static void createConnectionToExtension() { |
| byte [] address = Converter.javaStringToCString(EXTENSION_DBUS_SERVER_CLIENT_ADDRESS); |
| long [] error = new long [1]; |
| // Create connection asynchronously to avoid deadlock issues |
| OS.g_dbus_connection_new_for_address (address, OS.G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT, |
| 0, 0, newConnectionToExtensionCB.getAddress(), 0); |
| |
| if (error[0] != 0) { |
| System.err.println("SWT WebkitGDBus: error creating connection to the extension " |
| + Display.extractFreeGError(error[0])); |
| } |
| } |
| |
| /** |
| * Calls the web extension synchronously. Returns true if the operation succeeded, and false |
| * otherwise (or if the operation times out). |
| * |
| * @param params a pointer to the GVariant containing the parameters |
| * @param methodName a String representing the DBus method name in the extension |
| * @return an Object representing the return value from DBus in boolean form |
| */ |
| static Object callExtensionSync (long params, String methodName) { |
| long [] error = new long [1]; // GError ** |
| long gVariant = OS.g_dbus_connection_call_sync(connectionToExtension, EXTENSION_DBUS_NAME, EXTENSION_OBJECT_PATH, |
| EXTENSION_INTERFACE_NAME, Converter.javaStringToCString(methodName), params, |
| 0, OS.G_DBUS_CALL_FLAGS_NO_AUTO_START, 1000, 0, error); |
| if (error[0] != 0) { |
| String msg = Display.extractFreeGError(error[0]); |
| /* |
| * Don't print console warnings for timeout errors, as we can handle these ourselves. |
| * Note, most timeout errors happen only when running test cases, not during "normal" use. |
| */ |
| if (msg != null && (!msg.contains("Timeout") && !msg.contains("timeout"))) { |
| System.err.println("SWT WebKitGDBus: there was an error executing something synchronously with the extension."); |
| System.err.println("SWT WebKitGDBus: the error message is: " + msg); |
| return (Object) false; |
| } |
| return (Object) "timeout"; |
| } |
| Object resultObject = gVariant != 0 ? convertGVariantToJava(gVariant) : (Object) false; |
| // Sometimes we get back tuples from GDBus, which get converted into Object arrays. In this case |
| // we only care about the first value, since the extension never returns anything more than that. |
| if (resultObject instanceof Object[]) { |
| return ((Object []) resultObject)[0]; |
| } |
| return resultObject; |
| } |
| |
| /** |
| * Calls the web extension asynchronously. Note, this method returning true does not |
| * guarantee the operation's success, it only means no errors occurred. |
| * |
| * @param params a pointer to the GVariant containing the parameters |
| * @param methodName a String representing the DBus method name in the extension |
| * @return true if the extension was called without errors, false otherwise |
| */ |
| static boolean callExtensionAsync (long params, String methodName) { |
| long [] error = new long [1]; // GError ** |
| OS.g_dbus_connection_call(connectionToExtension, EXTENSION_DBUS_NAME, EXTENSION_OBJECT_PATH, |
| EXTENSION_INTERFACE_NAME, Converter.javaStringToCString(methodName), params, |
| 0, OS.G_DBUS_CALL_FLAGS_NO_AUTO_START, 1000, 0, callExtensionAsyncCB.getAddress(), 0); |
| if (error[0] != 0) { |
| String msg = Display.extractFreeGError(error[0]); |
| System.err.println("SWT WebKitGDBus: there was an error executing something asynchronously " |
| + "with the extension."); |
| System.err.println("SWT WebKitGDBus: the error message is: " + msg); |
| return false; |
| } |
| return true; |
| } |
| |
| /* TYPE NOTES |
| * |
| * GDBus doesn't support all the types that we need. I used encoded 'byte' to translate some types. |
| * |
| * - 'null' is not supported. I thought to potentially use 'maybe' types, but they imply a possible NULL of a certain type, but not null itself. |
| * so I use 'byte=48' (meaning '0' in ASCII) to denote null. |
| * |
| * - Empty arrays/structs are not supported by gdbus. |
| * "Container types ... Empty structures are not allowed; there must be at least one type code between the parentheses" |
| * src: https://dbus.freedesktop.org/doc/dbus-specification.html |
| * I used byte=101 (meaning 'e' in ASCII) to denote empty array. |
| * |
| * In Javascript all Number types seem to be 'double', (int/float/double/short -> Double). So we convert everything into double accordingly. |
| * |
| * DBus Type info: https://dbus.freedesktop.org/doc/dbus-specification.html#idm423 |
| * GDBus Type info: https://developer.gnome.org/glib/stable/glib-GVariantType.html |
| */ |
| |
| /** |
| * Converts the given GVariant to a Java object. |
| * (Only subset of types is currently supported). |
| * |
| * We assume that the given gvariant does not contain errors. (checked by webextension first). |
| * |
| * @param gVariant a pointer to the native GVariant |
| */ |
| static Object convertGVariantToJava(long gVariant){ |
| |
| if (OS.g_variant_is_of_type(gVariant, OS.G_VARIANT_TYPE_BOOLEAN)){ |
| return OS.g_variant_get_boolean(gVariant); |
| } |
| |
| // see: WebKitGTK.java 'TYPE NOTES' |
| if (OS.g_variant_is_of_type(gVariant, OS.G_VARIANT_TYPE_BYTE)) { |
| byte byteVal = OS.g_variant_get_byte(gVariant); |
| |
| switch (byteVal) { |
| case WebkitGDBus.SWT_DBUS_MAGIC_NUMBER_NULL: |
| return null; |
| case WebkitGDBus.SWT_DBUS_MAGIC_NUMBER_EMPTY_ARRAY: |
| return new Object [0]; |
| default: |
| System.err.println("SWT WebKitGDBus: received an unsupported byte type via GDBus: " + byteVal); |
| break; |
| } |
| } |
| |
| if (OS.g_variant_is_of_type(gVariant, OS.G_VARIANT_TYPE_DOUBLE)){ |
| return OS.g_variant_get_double(gVariant); |
| } |
| |
| if (OS.g_variant_is_of_type(gVariant, OS.G_VARIANT_TYPE_UINT64)){ |
| return OS.g_variant_get_uint64(gVariant); |
| } |
| |
| if (OS.g_variant_is_of_type(gVariant, OS.G_VARIANT_TYPE_STRING)){ |
| return Converter.cCharPtrToJavaString(OS.g_variant_get_string(gVariant, null), false); |
| } |
| |
| if (OS.g_variant_is_of_type(gVariant, OS.G_VARIANT_TYPE_TUPLE)){ |
| int length = (int)OS.g_variant_n_children (gVariant); |
| Object[] result = new Object[length]; |
| for (int i = 0; i < length; i++) { |
| result[i] = convertGVariantToJava (OS.g_variant_get_child_value(gVariant, i)); |
| } |
| return result; |
| } |
| |
| String typeString = Converter.cCharPtrToJavaString(OS.g_variant_get_type_string(gVariant), false); |
| SWT.error (SWT.ERROR_INVALID_ARGUMENT, new Throwable("Unhandled variant type " + typeString )); |
| return null; |
| } |
| |
| /** |
| * Converts the given Java Object to a GVariant * representation. |
| * (Only subset of types is currently supported). |
| * |
| * We assume that input Object may contain invalid types. |
| * |
| * @return pointer GVariant * |
| */ |
| static long convertJavaToGVariant(Object javaObject) throws SWTException { |
| |
| if (javaObject == null) { |
| return OS.g_variant_new_byte(WebkitGDBus.SWT_DBUS_MAGIC_NUMBER_NULL); // see: WebKitGTK.java 'TYPE NOTES' |
| } |
| |
| if (javaObject instanceof Long) { |
| return OS.g_variant_new_uint64((Long) javaObject); |
| } |
| |
| if (javaObject instanceof String) { |
| return OS.g_variant_new_string (Converter.javaStringToCString((String) javaObject)); |
| } |
| |
| if (javaObject instanceof Boolean) { |
| return OS.g_variant_new_boolean((Boolean) javaObject); |
| } |
| |
| // We treat Integer, Long, Double, Short as a 'double' because in Javascript these are all 'double'. |
| // Note, they all extend 'Number' java type, so they are an instance of it. |
| if (javaObject instanceof Number) { // see: WebKitGTK.java 'TYPE NOTES' |
| return OS.g_variant_new_double (((Number) javaObject).doubleValue()); |
| } |
| |
| if (javaObject instanceof Object[]) { |
| Object[] arrayValue = (Object[]) javaObject; |
| int length = arrayValue.length; |
| |
| if (length == 0) { |
| return OS.g_variant_new_byte(WebkitGDBus.SWT_DBUS_MAGIC_NUMBER_EMPTY_ARRAY); // see: WebKitGTK.java 'TYPE NOTES' |
| } |
| |
| long variants[] = new long [length]; |
| |
| for (int i = 0; i < length; i++) { |
| variants[i] = convertJavaToGVariant(arrayValue[i]); |
| } |
| |
| return OS.g_variant_new_tuple(variants, length); |
| } |
| System.err.println("SWT WebKitGDBus: invalid object being returned to JavaScript: " + javaObject.toString() + "\n" |
| + "Only the following are supported: null, String, Boolean, Number(Long,Integer,Double...), Object[] of basic types"); |
| throw new SWTException(SWT.ERROR_INVALID_ARGUMENT, "Given object is not valid: " + javaObject.toString()); |
| } |
| } |