/* Copyright (C) 2009 Christian Dywan This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. See the file COPYING for the full license text. */ /* midori.tabs midori.windows midori.currentWindow midori.createAction({name:'Example',label:'_Example',icon:'gtk-info',tooltip:'Examples',activate:function()}) action = midori.currentWindow.getAction(name) foreach (window as midori.windows) window.getAction('myaction').activate() midori.window[i].tabs midori.addWindow midori.addTab midori.removeWindow midori.removeTab */ #if HAVE_CONFIG_H #include #endif #include #include #include gchar* sokoke_js_string_utf8 (JSStringRef js_string) { size_t size_utf8; gchar* string_utf8; g_return_val_if_fail (js_string, NULL); size_utf8 = JSStringGetMaximumUTF8CStringSize (js_string); string_utf8 = g_new (gchar, size_utf8); JSStringGetUTF8CString (js_string, string_utf8, size_utf8); return string_utf8; } static void javascript_deactivate_cb (MidoriExtension* extension, MidoriBrowser* browser) { /* FIXME: Unload all javascript extensions */ } static JSValueRef midori_javascript_midori_action_cb (JSContextRef js_context, JSObjectRef js_function, JSObjectRef js_this, size_t n_arguments, const JSValueRef js_arguments[], JSValueRef* js_exception) { GtkAction* action; JSObjectRef js_meta; JSPropertyNameArrayRef js_properties; size_t n_properties; guint i; MidoriApp* app; if (n_arguments != 1) { *js_exception = JSValueMakeString (js_context, JSStringCreateWithUTF8CString ( "MidoriError: Wrong number of arguments; 'midori.createAction ({property:value, ...})'")); return JSValueMakeNull (js_context); } if (!JSValueIsObject (js_context, js_arguments[0])) { *js_exception = JSValueMakeString (js_context, JSStringCreateWithUTF8CString ( "MidoriError: Argument is not an object; 'midori.createAction ({property:value, ...})'")); return JSValueMakeNull (js_context); } action = g_object_new (GTK_TYPE_ACTION, NULL); js_meta = JSValueToObject (js_context, js_arguments[0], NULL); js_properties = JSObjectCopyPropertyNames (js_context, js_meta); n_properties = JSPropertyNameArrayGetCount (js_properties); for (i = 0; i < n_properties; i++) { JSStringRef js_name; gchar* name; JSValueRef js_property; JSStringRef js_string; gchar* string; js_name = JSPropertyNameArrayGetNameAtIndex (js_properties, i); name = sokoke_js_string_utf8 (js_name); if (g_str_equal (name, "label")) { js_property = JSObjectGetProperty (js_context, js_meta, js_name, NULL); js_string = JSValueToStringCopy (js_context, js_property, NULL); string = sokoke_js_string_utf8 (js_string); JSStringRelease (js_string); g_object_set (action, "label", string, NULL); g_free (string); } else if (g_str_equal (name, "icon")) { js_property = JSObjectGetProperty (js_context, js_meta, js_name, NULL); js_string = JSValueToStringCopy (js_context, js_property, NULL); string = sokoke_js_string_utf8 (js_string); JSStringRelease (js_string); /* FIXME: stock-id, or icon-name, or URI */ g_object_set (action, "stock-id", string, NULL); g_free (string); } else if (g_str_equal (name, "tooltip")) { js_property = JSObjectGetProperty (js_context, js_meta, js_name, NULL); js_string = JSValueToStringCopy (js_context, js_property, NULL); string = sokoke_js_string_utf8 (js_string); JSStringRelease (js_string); g_object_set (action, "tooltip", string, NULL); g_free (string); } else if (g_str_equal (name, "activate")) { /* FIXME */ } else { *js_exception = JSValueMakeString (js_context, JSStringCreateWithUTF8CString ( "MidoriError: Unknown property; 'midori.createAction ({property:value, ...})'")); return JSValueMakeNull (js_context); } } if ((app = JSObjectGetPrivate (js_this))) { /* TODO: Offer the user to add a toolbar button */ } /* TODO: add action to all existing and future browsers */ /* gtk_action_connect_accelerator */ return JSValueMakeNull (js_context); } static JSContextRef midori_javascript_context (MidoriApp* app) { JSContextRef js_context = JSGlobalContextCreateInGroup (NULL, NULL); JSClassDefinition js_class_def = kJSClassDefinitionEmpty; JSClassRef js_class; JSObjectRef js_object; JSStringRef js_name; JSStaticFunction functions[] = { { "createAction", midori_javascript_midori_action_cb, kJSPropertyAttributeNone }, { NULL, NULL, 0 } }; js_class_def.className = "midori"; js_class_def.staticFunctions = functions; js_class = JSClassCreate (&js_class_def); js_object = JSObjectMake (js_context, js_class, app); js_name = JSStringCreateWithUTF8CString ("midori"); JSObjectSetProperty (js_context, JSContextGetGlobalObject (js_context), js_name, js_object, kJSPropertyAttributeNone, NULL); JSStringRelease (js_name); return js_context; } static void midori_javascript_extension_activate_cb (MidoriExtension* extension, MidoriApp* app) { gchar* filename = g_object_get_data (G_OBJECT (extension), "filename"); gchar* fullname = g_build_path (G_DIR_SEPARATOR_S, g_get_user_data_dir (), PACKAGE_NAME, "extensions", filename, NULL); gchar* script; GError* error = NULL; if (g_file_get_contents (fullname, &script, NULL, &error)) { JSContextRef js_context = midori_javascript_context (app); gchar* exception = NULL; g_free (sokoke_js_script_eval (js_context, script, &exception)); if (exception) { g_object_set (extension, "description", exception, "version", NULL, NULL); g_warning ("%s", exception); g_free (exception); } } else { g_object_set (extension, "description", error->message, "version", NULL, NULL); g_warning ("%s", error->message); g_error_free (error); } g_free (fullname); g_free (script); } static void javascript_load_extensions (gchar** active, MidoriApp* app, const gchar* path) { GDir* dir; /* TODO: Monitor folder for new files or modifications at runtime */ if ((dir = g_dir_open (path, 0, NULL))) { KatzeArray* extensions = katze_object_get_object (app, "extensions"); JSContextRef js_context = midori_javascript_context (app); const gchar* filename; while ((filename = g_dir_read_name (dir))) { gchar* fullname; GError* error; gchar* script; MidoriExtension* extension; /* Ignore files which don't have the correct suffix */ if (!g_str_has_suffix (filename, ".js")) continue; fullname = g_build_filename (path, filename, NULL); error = NULL; extension = g_object_new (MIDORI_TYPE_EXTENSION, "name", filename, NULL); if (g_file_get_contents (fullname, &script, NULL, &error)) { JSStringRef js_script; JSValueRef js_exception; js_script = JSStringCreateWithUTF8CString (script); if (JSCheckScriptSyntax (js_context, js_script, NULL, 0, &js_exception)) { /* FIXME: Read meta data from .js file */ g_object_set (extension, "description", "", "version", "0.1", "authors", "", NULL); /* Signal that we want the extension to load and save */ g_object_set_data_full (G_OBJECT (extension), "filename", g_strdup (filename), g_free); if (midori_extension_is_prepared (extension)) midori_extension_get_config_dir (extension); g_signal_connect (extension, "activate", G_CALLBACK (midori_javascript_extension_activate_cb), NULL); } else { JSStringRef js_string = JSValueToStringCopy (js_context, js_exception, NULL); gchar* string = sokoke_js_string_utf8 (js_string); JSStringRelease (js_string); g_object_set (extension, "description", string, NULL); g_warning ("%s", string); g_free (string); } } else { g_object_set (extension, "description", error->message, NULL); g_warning ("%s", error->message); g_error_free (error); } g_free (fullname); katze_array_add_item (extensions, extension); if (active) { guint i = 0; gchar* name; while ((name = active[i++])) if (!g_strcmp0 (filename, name)) g_signal_emit_by_name (extension, "activate", app); } /* FIXME main.c needs to monitor extensions g_signal_connect_after (extension, "activate", G_CALLBACK (extension_activate_cb), app); g_signal_connect_after (extension, "deactivate", G_CALLBACK (extension_activate_cb), app); */ g_object_unref (extension); } g_object_unref (extensions); g_dir_close (dir); } } static void javascript_activate_cb (MidoriExtension* extension, MidoriApp* app) { gchar** active = midori_extension_get_string_list (extension, "extensions", NULL); /* FIXME Scan system data dirs */ gchar* path = g_build_path (G_DIR_SEPARATOR_S, g_get_user_data_dir (), PACKAGE_NAME, "extensions", NULL); javascript_load_extensions (active, app, path); g_free (path); g_strfreev (active); g_signal_connect (extension, "deactivate", G_CALLBACK (javascript_deactivate_cb), NULL); } MidoriExtension* extension_init (void) { MidoriExtension* extension = g_object_new (MIDORI_TYPE_EXTENSION, "name", _("Javascript extensions"), "description", _("Enable extensions written in Javascript"), "version", "0.1", "authors", "Christian Dywan ", NULL); midori_extension_install_string_list (extension, "extensions", NULL, G_MAXSIZE); g_signal_connect (extension, "activate", G_CALLBACK (javascript_activate_cb), NULL); return extension; }