diff options
-rw-r--r-- | Makefile | 2 | ||||
-rw-r--r-- | api/jsapi.txt | 441 | ||||
-rw-r--r-- | config.mk | 1 | ||||
-rw-r--r-- | scripts/lib/data.js | 60 | ||||
-rw-r--r-- | scripts/lib/enums.js | 6 | ||||
-rw-r--r-- | src/download.c | 2 | ||||
-rw-r--r-- | src/scripts.c | 104 |
7 files changed, 426 insertions, 190 deletions
@@ -31,7 +31,7 @@ install-data: all install -d $(DESTDIR)$(DATADIR)/$(REAL_NAME)/$(JSDIR) install -m 644 $(JSDIR)/$(HINT_SCRIPT) $(DESTDIR)$(DATADIR)/$(REAL_NAME)/$(JSDIR)/$(HINT_SCRIPT) install -d $(DESTDIR)$(DATADIR)/$(REAL_NAME)/$(LIBJSDIR) - install -m 644 $(LIBJSDIR)/signals.js $(DESTDIR)$(DATADIR)/$(REAL_NAME)/$(LIBJSDIR)/signals.js + install -m 644 $(LIBJSFILES) $(DESTDIR)$(DATADIR)/$(REAL_NAME)/$(LIBJSDIR)/ install -d $(DESTDIR)$(DATADIR)/$(REAL_NAME)/$(LIBDIR) install -m 644 $(LIBDIR)/$(INFO_FILE) $(DESTDIR)$(DATADIR)/$(REAL_NAME)/$(LIBDIR)/$(INFO_FILE) install -m 644 $(LIBDIR)/$(HEAD_FILE) $(DESTDIR)$(DATADIR)/$(REAL_NAME)/$(LIBDIR)/$(HEAD_FILE) diff --git a/api/jsapi.txt b/api/jsapi.txt index 04896cc4..5d6b72a2 100644 --- a/api/jsapi.txt +++ b/api/jsapi.txt @@ -8,12 +8,14 @@ This api documentation describes the javascript interface. == Getting Started == Scripts that use the javascript api must be located in +$XDG_CONFIG_HOME/dwb/userscripts+ like any other script. -To include a userscript that uses this api the script must have the following +To include a userscript put a script with shebang + ------- #!javascript ------- +in the userscripts directory. All scripts with this shebang will be executed in the same global context so it must be taken care to encapsulate the data if more than one script is used (see also <<Encapsulation>>). @@ -21,10 +23,130 @@ also <<Encapsulation>>). :numbered!: :caption: -[[Functions]] -== Functions == -=== Global functions === +== Signals == +With the +signals+ object *dwb* communicates with the script on certain events. +To connect to a signal one can call the connect function that is implemented by +the signals object, that takes 2 arguments, the name of the signal and a +callback function. + +The callback function always has 2 parameters, the object which +received the signal and another object which contains values relevant to the +signal. A callback function should either return +true+ or +false+ or nothing which is +equivalent to +false+. +If multiple callbacks are connected to the same signal and one callback +function returns +true+ the overall return value will be +true+. + +*dwb* only emits signals as long as one callback is connected to a signal. To +reduce overhead one should disconnect from the signal when it is no longer +needed. + +The signals object implements the following functions + +.Functions +[options="header", cols="1s,1m,3,2,2"] +|============================================= +|function 2+| parameter | returns | description + +.2+|connect +|String |The signal to connect to; *required* .2+| The +signal id of the signal or +-1+ if the signal was not connected. +.2+| Connects to a signal. + +|Function(+Object+, +Object+) | The callback to call when the signal is emitted; *required* + +|disconnect +|Number |The signal id of the signal; *required* | - +| Disconnects from a signal, the id is the id returned by ++connect+. + + +.3+|emit +|String |The signal to emit; *required* .3+| - +.3+| Emits a signal. +|Object |Object passed to the callback function; *optional* +|Object |Object passed to the callback function; *optional* + + +|============================================= + +The global signals implemented by *dwb* are + +[options="header", cols="2s,2m,2m,4,4"] +|============================================= +|signal |first parameter 2+|object properties |description + +.9+|buttonPress .9+| focused <<webview>> +|button|The button that is pressed, usually a value between 1 and 5 +.9+|Emitted when a button is pressed, only clicks on the webview are noticed; +return +true+ to stop the default action +|context|A <<ButtonContext>>, i.e. the element under the cursor when the button +was pressed +|state|A bitmap of modifiers pressed, see <<Modifier>> +|time|The time in milliseconds when the button was pressed +|type|A <<ClickType>> +|x|x-position relative to the window +|xRoot|x-position relative to the screen +|y|y-position relative to the window +|yRoot|y-position relative to the screen + +|buttonRelease | focused <<webview>> +2+d|Same as *buttonPress* but without +type+ +|Emitted when a button is released, only clicks on the webview are noticed; +return +true+ to stop the default action + +.5+|download .5+| focused <<webview>> +|filename|The suggested filename +.5+|Emitted before a download starts. Return +true+ to handle the signal or +false+ to let *dwb* handle the signal. +|mimeType|The mimetype of the file or +unknown+ +|referer|The referer of the request +|uri|The uri of the file to download +|totalSize|The total size of the file + +.4+|downloadStatus .4+| focused <<webview>> +|filename|The filename +.4+|Emitted when the download status changes +|mimeType|Mimetype of the file or +unknown+ +|status|A <<DownloadStatus>> +|uri|Original uri of the file + +.5+|keyPress .5+| focused <<webview>> +|isModifier | Whether or not the key is a modifier +.5+|Emitted when a key is pressed, the first parameter of the callback function +will always be the currently focued <<webview>>; return +true+ to stop the +default action +|keyCode | Hardware keycode +|keyVal | Keycode as listed in gdkkeysyms.h +|name | A string represantation of the key +|state | A bitmap of modifiers pressed, see <<Modifier>> + +|keyRelease | focused <<webview>> +2+d|Same as *keyPress* +|Emitted when a key is released; return +true+ to stop the +default action + + +|tabFocus | focused <<webview>> +|last| Number of the previously focused tab +|Emitted when a new tab gets focus, the first parameter will be the new +<<webview>>; return +true+ to prevent the tab being changed +|============================================= + + +.Example +[source,javascript] +------ +function loadStatusCallback(wv, values) { + if (obj.status == LoadStatus.finished) { + io.print(values.uri); + signals.disconnect(id); + } +} +var id = signals.connect("loadStatus", loadStatusCallback); +------ + + +== Global functions == Functions from the global object. [options="header", cols="1s,1m,3,3,3"] @@ -61,7 +183,7 @@ the script will be visible in all scripts, +false+ to encapsulate the script; .2+|An +id+ for the timeout or -1 if an error occured .2+|Executes a function repeatedly until the function returns false or *timerStop* is called on the +id+ returned from *timerStart* -|Function |Function to execute +|Function() |Function to execute |timerStop |Number |The +id+ returned from *timerStart* @@ -82,6 +204,42 @@ timerStart(2000, function() { }); --------------------------------- +[[Globalobjects]] +== Global Objects == + +[[data]] +=== data === +The +data+ object can be used to determine internally used data securely. All +properties are readonly Strings. + +.Properties +[options="header", cols="2s,10"] +|============================================= +|bookmarks |Bookmark file +|cacheDir |Cache directory +|configDir |Config directory +|cookies |Cookie file +|cookiesWhitelist |Whitelist for persistent cookies +|customKeys |Custom keyboard shortcuts +|history |History file +|keys |Shortcuts configuration file +|pluginsWhitelist |Whitelist for the plugin blocker +|profile |Profile which will be +default+ unless another profile is specified on command line +|quickmarks |Quickmark file +|scriptWhitelist |Whitelist for scripts +|session |File with stored sessions for this profile +|sessionCookiesWhitelist |Whitelist for session cookies +|settings |Settings configuration file +|searchEngines |Searchengines +|============================================= + +.Example +[source,javascript] +--------------------------------- +// Get contents of the currently used bookmark file +var bookmarks = io.read(data.bookmarks); +-------------------------------- + [[io]] === io === The +io+ object implements functions for input and output. @@ -124,10 +282,12 @@ The +system+ object implements system functions. [options="header", cols="1s,1m,2,2,2"] |============================================= |function 2+| parameter | returns |description -|spawn +.3+|spawn |String | Command to execute; *required* -|+true+ if successfull, +false+ otherwise -|Executes a shell command using the default searchpath. +.3+|Returns <<SpawnError>> +.3+|Executes a shell command using the default searchpath +|Function(+String+) | callback for stdout, pass +null+ if only stderr is needed; *optional* +|Function(+String+) | callback for stderr; *optional* |getEnv |String | Name of the variable; *required* @@ -139,9 +299,50 @@ The +system+ object implements system functions. [source,javascript] ------------ var home = system.getEnv("HOME"); -system.spawn("xterm -e vim " + home + "/.bashrc"); +// async wrapped read +function asyncread(filename) { + system.spawn("cat " + filename, function (response) { + ... + }); +} +asyncread(home + "/.bashrc"); ------------ + +[[tabs]] +=== tabs === +The +tabs+ object implements functions and properties to get +webview+ objects. + +.Properties +[options="header", cols="2s,2m,1s,10"] +|============================================= +|name |type|mode| description +|current |<<webview>>| ro |The currently focused tab +|length |Number |ro| The total number of tabs +|number |Number |ro| The number of the currently focused tab + +|============================================= + +.Functions +[options="header", cols="1s,1m,3,2,2"] +|============================================= +|function 2+| parameter | returns |description +|nth |Number |Number of the tab, counting from 0; *required* | A <<webview>> object or +null+ if +an error occured | Gets the webview object of the nth tab. + +|============================================= + + +.Example +[source,javascript] +---- +var c = tabs.current; +tabs.nth(2).uri = c.uri; +---- + +[[Webkitobjects]] +== Webkit objects == + [[webview]] === webview === The +webview+ object represents the widget that actually displays the site @@ -202,6 +403,33 @@ in a function; *optional*, default +false+ |============================================= + +.Signals +[options="header", cols="2s,2m,2m,4,4"] +|============================================= +|signal |first parameter 2+|object properties |description +.2+|[[loadstatus]]loadStatus .2+| <<webview>> +|status|The <<LoadStatus>> +.2+|Emitted when the load status changes. +|uri|Uri that is currently loaded + +.2+|mimeType .2+| <<webview>> +|mimeType| The mime-type +.2+|Decide whether or not to show a given mimetype. Return true to stop the request. +|uri| Uri of the request + +.2+|navigation .2+| <<webview>> +|uri | Uri of the request +.2+| Emitted before a new site is loaded, return +true+ to stop the request. +|reason | Reason for the request where +reason+ is a <<NavigationReason>> + + +|resource | <<webview>> +|uri | Uri of the request +|Emitted before a new resource is loaded, return +true+ to stop the request. +|============================================= + + .Example [source,javascript] ------------ @@ -246,190 +474,32 @@ different domain. For this purpose the +frame+ object can be used. .2+|inject |String | Script to inject .2+|+true+ if the script was injected, +false+ otherwise. -.2+|Injects a script into the +frame+, see also <<webview>> +.2+|Injects a script into the +frame+, see also <<frameStatus>> |Boolean | +true+ to inject the script in the global scope, +false+ to encapsulate it in a function; *optional*, default +false+ |============================================= - - -[[tabs]] -=== tabs === -The +tabs+ object implements functions and properties to get +webview+ objects. - -.Properties -[options="header", cols="2s,2m,1s,10"] -|============================================= -|name |type|mode| description -|current |<<webview>>| ro |The currently focused tab -|length |Number |ro| The total number of tabs -|number |Number |ro| The number of the currently focused tab - -|============================================= - -.Functions -[options="header", cols="1s,1m,3,2,2"] -|============================================= -|function 2+| parameter | returns |description -|nth |Number |Number of the tab, counting from 0; *required* | A <<webview>> object or +null+ if -an error occured | Gets the webview object of the nth tab. - -|============================================= - -.Example -[source,javascript] ----- -var c = tabs.current; -tabs.nth(2).uri = c.uri; ----- - - -== Signals == -With the +signals+ object *dwb* communicates with the script on certain events. To connect to a signal -one can set callback function as the signals property which will be called then. -However it is better to connect to signals using the connect function. - -The callback function always has 2 parameters, the object which -received the signal and another object which contains values relevant to the -signal. A callback function should either return +true+ or +false+ or nothing which is -equivalent to +false+. -If multiple callbacks are connected to the same signal and one callback -function returns +true+ the overall return value will be +true+. - - -The signals object implements the following functions - -.Functions -[options="header", cols="1s,1m,3,2,2"] -|============================================= -|function 2+| parameter | returns | description - -.2+|connect -|String |The signal to connect to; *required* .2+| The -signal id of the signal or +-1+ if the signal was not connected. -.2+| Connects to a signal. - -|Function | The callback to call when the signal is emitted; *required* - -|disconnect -|Number |The signal id of the signal; *required* | - -| Disconnects from a signal, the id is the id returned by -+connect+. - - -.3+|emit -|String |The signal to emit; *required* .3+| - -.3+| Emits a signal. -|Object |Object passed to the callback function; *optional* -|Object |Object passed to the callback function; *optional* - - -|============================================= - - - -The signals implemented by *dwb* are - +.Signals [options="header", cols="2s,2m,2m,4,4"] |============================================= |signal |first parameter 2+|object properties |description - -.9+|buttonPress .9+| focused <<webview>> -|button|The button that is pressed, usually a value between 1 and 5 -.9+|Emitted when a button is pressed, only clicks on the webview are noticed; -return +true+ to stop the default action -|context|A <<ButtonContext>>, i.e. the element under the cursor when the button -was pressed -|state|A bitmap of modifiers pressed, see <<Modifier>> -|time|The time in milliseconds when the button was pressed -|type|A <<ClickType>> -|x|x-position relative to the window -|xRoot|x-position relative to the screen -|y|y-position relative to the window -|yRoot|y-position relative to the screen - -|buttonRelease | focused <<webview>> -2+d|Same as *buttonPress* but without +type+ -|Emitted when a button is released, only clicks on the webview are noticed; -return +true+ to stop the default action - -.5+|download .5+| focused <<webview>> -|filename|The suggested filename -.5+|Emitted before a download starts. Return +true+ to handle the signal or +false+ to let *dwb* handle the signal. -|mimeType|The mimetype of the file or +unknown+ -|referer|The referer of the request -|uri|The uri of the file to download -|totalSize|The total size of the file - -.4+|downloadStatus .4+| focused <<webview>> -|filename|The filename -.4+|Emitted when the download status changes, the +webview+ is always the focused -+webview+ -|mimeType|Mimetype of the file or +unknown+ -|status|A <<DownloadStatus>> -|uri|Original uri of the file - -.4+|frameStatus .4+| <<frame>> -|name |Name of the frame -.4+| Emitted when the load status changes +|frameStatus[[frameStatus]] | <<frame>> |status |<<LoadStatus>> -|title |Title of the frame -|uri |Uri of the frame +| Emitted when the load status of a frame changes changes, scripts should be +injected into a frame either on +LoadStatus.committed+ or +LoadStatus.finished+ -.5+|keyPress .5+| focused <<webview>> -|isModifier | Whether or not the key is a modifier -.5+|Emitted when a key is pressed, the first parameter of the callback function -will always be the currently focued <<webview>>; return +true+ to stop the -default action -|keyCode | Hardware keycode -|keyVal | Keycode as listed in gdkkeysyms.h -|name | A string represantation of the key -|state | A bitmap of modifiers pressed, see <<Modifier>> - -|keyRelease | focused <<webview>> -2+d|Same as *keyPress* -|Emitted when a key is released; return +true+ to stop the -default action - - -.2+|[[loadstatus]]loadStatus .2+| focused <<webview>> -|status|The <<LoadStatus>> -.2+|Emitted when the load status changes. -|uri|Uri that is currently loaded - -.2+|mimeType .2+| focused <<webview>> -|mimeType| The mime-type -.2+|Decide whether or not to show a given mimetype. Return true to stop the request. -|uri| Uri of the request - -.2+|navigation .2+| focused <<webview>> -|uri | Uri of the request -.2+| Emitted before a new site is loaded, return +true+ to stop the request. -|reason | Reason for the request where +reason+ is a <<NavigationReason>> - - -|resource | focused <<webview>> -|uri | Uri of the request -|Emitted before a new resource is loaded, return +true+ to stop the request. - -|tabFocus | focused <<webview>> -|last| Number of the previously focused tab -|Emitted when a new tab gets focus, the first parameter will be the new -<<webview>>; return +true+ to prevent the tab being changed |============================================= - -.Example +.Example [source,javascript] ------- -function loadStatusCallback(wv, values) { - if (obj.status == LoadStatus.finished) { - io.print(values.uri); - signals.disconnect(id); +-------- +signals.connect("frameStatus", function(frame, o) { + if (o.status == LoadStatus.finished) { + frame.inject("document.body.innerHTML = '';"); } -} -var id = signals.connect("loadStatus", loadStatusCallback); ------- +}); +-------- + == Enum objects == Enum objects are objects that have only readonly properties, mapping @@ -532,6 +602,17 @@ const NavigationReason = { }; -------- +[[SpawnError]] +.SpawnError +[source,javascript] +-------- +const SpawnError = { + success : 0, + spawnFailed : 1<<0, + stdoutFailed : 1<<1, + stderrFailed : 1<<2 +}; +-------- [[Encapsulation]] == Encapsulation == @@ -9,6 +9,7 @@ SRCDIR=src JSDIR=scripts LIBDIR=lib LIBJSDIR=$(JSDIR)/$(LIBDIR) +LIBJSFILES=$(LIBJSDIR)/signals.js $(LIBJSDIR)/enums.js $(LIBJSDIR)/data.js SHAREDIR=share UTILDIR=util SUBDIRS=$(SRCDIR) $(UTILDIR) diff --git a/scripts/lib/data.js b/scripts/lib/data.js new file mode 100644 index 00000000..8079a996 --- /dev/null +++ b/scripts/lib/data.js @@ -0,0 +1,60 @@ +Object.defineProperty(data, "bookmarks", { + value : data.configDir + "/" + data.profile + "/bookmarks", + enumerable : true +}); +Object.defineProperty(data, "history", { + value : data.configDir + "/" + data.profile + "/history", + enumerable : true +}); +Object.defineProperty(data, "cookies", { + value : data.configDir + "/" + data.profile + "/cookies", + enumerable : true +}); +Object.defineProperty(data, "quickmarks", { + value : data.configDir + "/" + data.profile + "/cookies", + enumerable : true +}); +Object.defineProperty(data, "cookies", { + value : data.configDir + "/" + data.profile + "/cookies", + enumerable : true +}); +Object.defineProperty(data, "cookiesWhitelist", { + value : data.configDir + "/" + data.profile + "/cookies.allow", + enumerable : true +}); +Object.defineProperty(data, "sessionCookiesWhitelist", { + value : data.configDir + "/" + data.profile + "/cookies_session.allow", + enumerable : true +}); +Object.defineProperty(data, "sessionCookiesWhitelist", { + value : data.configDir + "/" + data.profile + "/cookies_session.allow", + enumerable : true +}); +Object.defineProperty(data, "pluginsWhitelist", { + value : data.configDir + "/" + data.profile + "/plugins.allow", + enumerable : true +}); +Object.defineProperty(data, "scriptWhitelist", { + value : data.configDir + "/" + data.profile + "/scripts.allow", + enumerable : true +}); +Object.defineProperty(data, "session", { + value : data.configDir + "/" + data.profile + "/session", + enumerable : true +}); +Object.defineProperty(data, "customKeys", { + value : data.configDir + "/" + data.profile + "/custom_keys", + enumerable : true +}); +Object.defineProperty(data, "keys", { + value : data.configDir + "/keys", + enumerable : true +}); +Object.defineProperty(data, "settings", { + value : data.configDir + "/settings", + enumerable : true +}); +Object.defineProperty(data, "searchEngines", { + value : data.configDir + "/searchengines", + enumerable : true +}); diff --git a/scripts/lib/enums.js b/scripts/lib/enums.js index 8c634d27..3760bf9c 100644 --- a/scripts/lib/enums.js +++ b/scripts/lib/enums.js @@ -53,3 +53,9 @@ const DownloadStatus = { cancelled : 2, finished : 3 }; +const SpawnError = { + success : 0, + spawnFailed : 1<<0, + stdoutFailed : 1<<1, + stderrFailed : 1<<2 +}; diff --git a/src/download.c b/src/download.c index e273847f..692d4d6f 100644 --- a/src/download.c +++ b/src/download.c @@ -245,7 +245,7 @@ download_delay(DwbDownload *download) { static void download_status_cb(WebKitDownload *download, GParamSpec *p, DwbDownloadStatus *dstatus) { WebKitDownloadStatus status = webkit_download_get_status(download); - SCRIPTS_EMIT_NO_RETURN(dwb.state.fview, DOWNLOAD_STATUS, 4, + SCRIPTS_EMIT_NO_RETURN(SCRIPT(dwb.state.fview), DOWNLOAD_STATUS, 4, INTEGER, "status", status, CHAR, "filename", webkit_download_get_destination_uri(download), CHAR, "uri", webkit_download_get_uri(download), diff --git a/src/scripts.c b/src/scripts.c index afc48615..eff1e803 100644 --- a/src/scripts.c +++ b/src/scripts.c @@ -24,9 +24,9 @@ #include "scripts.h" #include "util.h" #include "js.h" -#define kJSDefaultFunction (kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete | kJSPropertyAttributeDontEnum) +#define kJSDefaultFunction (kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete ) //#define kJSDefaultFunction (kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete ) -#define kJSDefaultProperty (kJSPropertyAttributeDontDelete | kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum) +#define kJSDefaultProperty (kJSPropertyAttributeDontDelete | kJSPropertyAttributeReadOnly ) #define SCRIPT_WEBVIEW(o) (WEBVIEW(((GList*)JSObjectGetPrivate(o)))) @@ -49,6 +49,12 @@ static Sigmap _sigmap[] = { { SCRIPT_SIG_TAB_FOCUS, "tabFocus" }, { SCRIPT_SIG_FRAME_STATUS, "frameStatus" }, }; +enum { + SPAWN_SUCCESS = 0, + SPAWN_FAILED = 1<<0, + SPAWN_STDOUT_FAILED = 1<<1, + SPAWN_STDERR_FAILED = 1<<2, +}; static JSObjectRef _sigObjects[SCRIPT_SIG_LAST]; static JSGlobalContextRef _global_context; @@ -350,6 +356,21 @@ global_timer_start(JSContextRef ctx, JSObjectRef f, JSObjectRef thisObject, size return JSValueMakeNumber(ctx, ret); } static JSValueRef +global_get_profile(JSContextRef ctx, JSObjectRef object, JSStringRef js_name, JSValueRef* exception) { + return js_char_to_value(ctx, dwb.misc.profile); +} +static JSValueRef +global_get_cache_dir(JSContextRef ctx, JSObjectRef object, JSStringRef js_name, JSValueRef* exception) { + return js_char_to_value(ctx, dwb.files.cachedir); +} +static JSValueRef +global_get_config_dir(JSContextRef ctx, JSObjectRef object, JSStringRef js_name, JSValueRef* exception) { + char *dir = util_build_path(); + JSValueRef ret = js_char_to_value(ctx, dir); + g_free(dir); + return ret; +} +static JSValueRef system_get_env(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argc, const JSValueRef argv[], JSValueRef* exc) { if (argc < 1) return JSValueMakeNull(ctx); @@ -362,18 +383,76 @@ system_get_env(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, s return JSValueMakeNull(ctx); return js_char_to_value(ctx, env); } +gboolean +spawn_output(GIOChannel *channel, GIOCondition condition, JSObjectRef callback) { + char *content; + gsize length; + if (condition == G_IO_HUP || condition == G_IO_ERR || condition == G_IO_NVAL) { + g_io_channel_unref(channel); + return false; + } + else if (g_io_channel_read_to_end(channel, &content, &length, NULL) == G_IO_STATUS_NORMAL && content != NULL) { + JSValueRef arg = js_char_to_value(_global_context, content); + if (arg != NULL) { + JSValueRef argv[] = { arg }; + JSObjectCallAsFunction(_global_context, callback, NULL, 1, argv , NULL); + } + g_free(content); + } + return false; +} static JSValueRef system_spawn(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argc, const JSValueRef argv[], JSValueRef* exc) { - gboolean ret = false; + int ret = 0; + int outfd, errfd; + char **srgv = NULL; + int srgc; + GIOChannel *out_channel, *err_channel; + JSObjectRef oc = NULL, ec = NULL; if (argc < 1) - return JSValueMakeBoolean(ctx, false); + return JSValueMakeBoolean(ctx, SPAWN_FAILED); + if (argc > 1) { + oc = JSValueToObject(ctx, argv[1], NULL); + if ( oc == NULL || !JSObjectIsFunction(ctx, oc) ) { + if (!JSValueIsNull(ctx, argv[1])) + ret |= SPAWN_STDOUT_FAILED; + oc = NULL; + } + } + if (argc > 2) { + ec = JSValueToObject(ctx, argv[2], NULL); + if ( ec == NULL || !JSObjectIsFunction(ctx, ec) ) { + if (!JSValueIsNull(ctx, argv[2])) + ret |= SPAWN_STDERR_FAILED; + ec = NULL; + } + } char *cmdline = js_value_to_char(ctx, argv[0], -1); - if (cmdline) { - ret = g_spawn_command_line_async(cmdline, NULL); - g_free(cmdline); + if (cmdline == NULL) { + ret |= SPAWN_FAILED; + goto error_out; } - return JSValueMakeBoolean(ctx, ret); + + if (!g_shell_parse_argv(cmdline, &srgc, &srgv, NULL) || + !g_spawn_async_with_pipes(NULL, srgv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, NULL, oc != NULL ? &outfd : NULL, ec != NULL ? &errfd : NULL, NULL)) { + ret |= SPAWN_FAILED; + goto error_out; + } + return JSValueMakeNumber(ctx, ret); + if (oc != NULL) { + out_channel = g_io_channel_unix_new(outfd); + g_io_add_watch(out_channel, G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, (GIOFunc)spawn_output, oc); + g_io_channel_set_close_on_unref(out_channel, true); + } + if (ec != NULL) { + err_channel = g_io_channel_unix_new(errfd); + g_io_add_watch(err_channel, G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, (GIOFunc)spawn_output, ec); + g_io_channel_set_close_on_unref(err_channel, true); + } +error_out: + g_free(cmdline); + return JSValueMakeNumber(ctx, ret); }/*}}}*/ /* IO {{{*/ @@ -514,8 +593,17 @@ create_global_object() { _global_context = JSGlobalContextCreate(class); JSClassRelease(class); + JSObjectRef global_object = JSContextGetGlobalObject(_global_context); + JSStaticValue data_values[] = { + { "profile", global_get_profile, NULL, kJSDefaultFunction }, + { "cacheDir", global_get_cache_dir, NULL, kJSDefaultFunction }, + { "configDir", global_get_config_dir, NULL, kJSDefaultFunction }, + { 0, 0, 0, 0 }, + }; + class = create_class("io", NULL, data_values); + create_object(_global_context, class, global_object, "data", NULL); JSStaticFunction wv_functions[] = { { "set", tab_set, kJSDefaultFunction }, |