diff --git a/Gruntfile.js b/Gruntfile.js
index 210bc36..ee9a0a4 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -62,6 +62,8 @@ module.exports = function (grunt) {
cleanXrandrBridge: nodeGypShell(gypCleanCmd, "gpii/node_modules/xrandr/nodexrandr"),
compilePackageKitBridge: nodeGypShell(gypCompileCmd, "gpii/node_modules/packagekit/nodepackagekit"),
cleanPackageKitBridge: nodeGypShell(gypCleanCmd, "gpii/node_modules/packagekit/nodepackagekit"),
+ compileProcesses: nodeGypShell(gypCompileCmd, "gpii/node_modules/processReporter/nodeprocesses"),
+ cleanProcesses: nodeGypShell(gypCleanCmd, "gpii/node_modules/processReporter/nodeprocesses"),
installUsbLib: {
command: [
"sudo cp " + usbListenerDir + "/gpii-usb-user-listener /usr/bin/",
@@ -98,6 +100,7 @@ module.exports = function (grunt) {
grunt.task.run("shell:compileAlsaBridge");
grunt.task.run("shell:compileXrandrBridge");
grunt.task.run("shell:compilePackageKitBridge");
+ grunt.task.run("shell:compileProcesses");
});
grunt.registerTask("clean-addons", "Clean the native addons", function () {
@@ -105,6 +108,7 @@ module.exports = function (grunt) {
grunt.task.run("shell:cleanAlsaBridge");
grunt.task.run("shell:cleanXrandrBridge");
grunt.task.run("shell:cleanPackageKitBridge");
+ grunt.task.run("shell:cleanProcesses");
});
grunt.registerTask("clean", "Clean the GPII binaries and uninstall", function () {
diff --git a/gpii/node_modules/gsettingsBridge/gsettings_bridge.js b/gpii/node_modules/gsettingsBridge/gsettings_bridge.js
index 5550872..4ead846 100644
--- a/gpii/node_modules/gsettingsBridge/gsettings_bridge.js
+++ b/gpii/node_modules/gsettingsBridge/gsettings_bridge.js
@@ -20,8 +20,8 @@ var fluid = require("universal");
var gpii = fluid.registerNamespace("gpii");
var nodeGSettings = require("./nodegsettings/build/Release/nodegsettings.node");
-fluid.registerNamespace("gpii.launch");
fluid.registerNamespace("gpii.gsettings");
+fluid.registerNamespace("gpii.gsettings.launch");
fluid.defaults("gpii.gsettings.setSingleKey", {
gradeNames: "fluid.function",
@@ -48,59 +48,92 @@ gpii.gsettings.setSingleKey = function (schemaId, key, value) {
nodeGSettings.set_gsetting(schemaId, key, value);
};
-gpii.gsettings.get = function (settingsarray) {
- var app = fluid.copy(settingsarray);
- for (var appId in app) {
- for (var j = 0; j < app[appId].length; j++) {
- var schemaId = app[appId][j].options.schema;
- var settings = app[appId][j].settings;
- var keys = nodeGSettings.get_gsetting_keys(schemaId);
-
- if (settings === null) {
- settings = {};
- for (var k = 0; k < keys.length; k++) {
- var key = keys[k];
- settings[key] = nodeGSettings.get_gsetting(schemaId, key);
- }
+gpii.gsettings.get = function (settingsArray) {
+ return gpii.settingsHandlers.transformPayload(settingsArray, function (entry) {
+ var schemaId = entry.options.schema;
+ var settings = entry.settings;
+ var keys = nodeGSettings.get_gsetting_keys(schemaId);
+
+ if (settings === null) {
+ settings = {};
+ for (var k = 0; k < keys.length; k++) {
+ var key = keys[k];
+ settings[key] = nodeGSettings.get_gsetting(schemaId, key);
}
- else {
- for (var settingKey in settings) {
- if (keys.indexOf(settingKey) === -1) {
- continue;
- }
- settings[settingKey] = nodeGSettings.get_gsetting(schemaId, settingKey);
+ }
+ else {
+ for (var settingKey in settings) {
+ if (keys.indexOf(settingKey) === -1) {
+ continue;
}
+ settings[settingKey] = nodeGSettings.get_gsetting(schemaId, settingKey);
}
- var noOptions = { settings: settings };
- app[appId][j] = noOptions;
}
- }
- return app;
+ return { settings: settings };
+ });
};
-gpii.gsettings.set = function (settingsarray) {
- var app = fluid.copy(settingsarray);
- for (var appId in app) {
- for (var j = 0; j < app[appId].length; j++) {
- var schemaId = app[appId][j].options.schema;
- var settings = app[appId][j].settings;
- var keys = nodeGSettings.get_gsetting_keys(schemaId);
+gpii.gsettings.set = function (settingsArray) {
+ return gpii.settingsHandlers.transformPayload(settingsArray, function (entry) {
+ var schemaId = entry.options.schema;
+ var settings = entry.settings;
+ var keys = nodeGSettings.get_gsetting_keys(schemaId);
- for (var settingKey in settings) {
- if (keys.indexOf(settingKey) === -1) {
- continue;
- }
- var value = settings[settingKey];
- var oldValue = nodeGSettings.get_gsetting(schemaId, settingKey);
- nodeGSettings.set_gsetting(schemaId, settingKey, value);
- settings[settingKey] = {
- "oldValue": oldValue,
- "newValue": value
- };
+ for (var settingKey in settings) {
+ if (keys.indexOf(settingKey) === -1) {
+ continue;
}
- var noOptions = { settings: settings};
- app[appId][j] = noOptions;
+ var value = settings[settingKey];
+ var oldValue = nodeGSettings.get_gsetting(schemaId, settingKey);
+ nodeGSettings.set_gsetting(schemaId, settingKey, value);
+ settings[settingKey] = {
+ "oldValue": oldValue,
+ "newValue": value
+ };
}
- }
- return app;
+ return { settings: settings};
+ });
+};
+
+
+gpii.gsettings.launch.get = function (settingsArray) {
+ return gpii.settingsHandlers.transformPayload(settingsArray, function (entry) {
+ var options = entry.options;
+ var keys = nodeGSettings.get_gsetting_keys(options.schema);
+ if (keys.indexOf(options.key) === -1) {
+ fluid.log(fluid.logLevel.FAIL, "ERROR: invalid key " + options.key +
+ " sent to gsettings launch (get) handler for schema " + options.schema);
+ return {};
+ }
+
+ return {
+ settings: {
+ running: nodeGSettings.get_gsetting(options.schema, options.key)
+ }
+ };
+ });
+};
+
+gpii.gsettings.launch.set = function (settingsArray) {
+ return gpii.settingsHandlers.transformPayload(settingsArray, function (entry) {
+ var options = entry.options;
+ var action = entry.settings.running;
+ var keys = nodeGSettings.get_gsetting_keys(options.schema);
+ if (keys.indexOf(options.key) === -1) {
+ fluid.log(fluid.logLevel.FAIL, "ERROR: invalid key " + options.key +
+ " sent to gsettings launch (set) handler for schema " + options.schema);
+ return {};
+ }
+
+ var oldValue = nodeGSettings.get_gsetting(options.schema, options.key);
+ nodeGSettings.set_gsetting(options.schema, options.key, action);
+ return {
+ settings: {
+ running: {
+ "oldValue": oldValue,
+ "newValue": action
+ }
+ }
+ };
+ });
};
diff --git a/gpii/node_modules/gsettingsBridge/tests/data/net.gpii.testing.gsettings.gschema.xml b/gpii/node_modules/gsettingsBridge/tests/data/net.gpii.testing.gsettings.gschema.xml
index 5b3da3b..84d4c61 100644
--- a/gpii/node_modules/gsettingsBridge/tests/data/net.gpii.testing.gsettings.gschema.xml
+++ b/gpii/node_modules/gsettingsBridge/tests/data/net.gpii.testing.gsettings.gschema.xml
@@ -111,6 +111,14 @@
+
+
+ false
+ Boolean settings
+ Test field - true or false - used for testing the gsettings launch handler
+
+
+
"hello world"
diff --git a/gpii/node_modules/gsettingsBridge/tests/gsettingsTests.js b/gpii/node_modules/gsettingsBridge/tests/gsettingsTests.js
index 7cf1b57..7e19d91 100644
--- a/gpii/node_modules/gsettingsBridge/tests/gsettingsTests.js
+++ b/gpii/node_modules/gsettingsBridge/tests/gsettingsTests.js
@@ -165,6 +165,59 @@ var get2 = {
}
};
+var launchSet = {
+ request: {
+ "org.gnome.orca": [
+ {
+ "settings": {
+ "running": true
+ },
+ "options": {
+ "schema": "net.gpii.testing.gsettings.launch",
+ "key": "start-flag"
+ }
+ }
+ ]
+ },
+ expected: {
+ "org.gnome.orca": [
+ {
+ "settings": {
+ "running": {
+ "oldValue": false,
+ "newValue": true
+ }
+ }
+ }
+ ]
+ }
+};
+
+var launchGet = {
+ request: {
+ "org.gnome.orca": [
+ {
+ settings: {
+ running: null
+ },
+ options: {
+ "schema": "net.gpii.testing.gsettings.launch",
+ "key": "start-flag"
+ }
+ }
+ ]
+ },
+ expected: {
+ "org.gnome.orca": [
+ {
+ settings: {
+ "running": false
+ }
+ }
+ ]
+ }
+};
+
// http://issues.gpii.net/browse/GPII-8
var gpii8 = {
request: {
@@ -231,6 +284,14 @@ jqUnit.test("Getting multiple keys with settings block as null", function () {
jqUnit.assertDeepEq("The correct values is expected in the returned payload: ", get2.expected, response);
});
+jqUnit.test("Gsettings launch handler", function () {
+ var response = gpii.gsettings.launch.get(launchGet.request);
+ jqUnit.assertDeepEq("The return value and format of the .get payload is as expected: ", launchGet.expected, response);
+
+ var setResponse = gpii.gsettings.launch.set(launchSet.request);
+ jqUnit.assertDeepEq("The return value and format of the .set payload is as expected: ", launchSet.expected, setResponse);
+});
+
jqUnit.test("GPII-8 Not core dumping on a non-existent key", function () {
var response = gpii.gsettings.set(gpii8.request);
jqUnit.assertValue("Didn't core dump.", response);
diff --git a/gpii/node_modules/gsettingsBridge/tests/runUnitTests.sh b/gpii/node_modules/gsettingsBridge/tests/runUnitTests.sh
index 5a7665c..3247934 100755
--- a/gpii/node_modules/gsettingsBridge/tests/runUnitTests.sh
+++ b/gpii/node_modules/gsettingsBridge/tests/runUnitTests.sh
@@ -27,6 +27,7 @@ gsettings reset-recursively net.gpii.testing.gsettings.multi-get2
gsettings reset-recursively net.gpii.testing.gsettings.multi-set1
gsettings reset-recursively net.gpii.testing.gsettings.multi-set2
gsettings reset-recursively net.gpii.testing.gsettings.multi-set3
+gsettings reset-recursively net.gpii.testing.gsettings.launch
#Run the tests:
node gsettingsTests.js
diff --git a/gpii/node_modules/processReporter/README.md b/gpii/node_modules/processReporter/README.md
new file mode 100644
index 0000000..b08c381
--- /dev/null
+++ b/gpii/node_modules/processReporter/README.md
@@ -0,0 +1,126 @@
+# GPII Process Reporter
+
+The GPII prcoesses reporter is made up of two parts:
+
+* a Nodejs bridge to GNOME/Linux's [libgtop library](https://developer.gnome.org/libgtop/stable/) to acquire information about running processes.
+* a fluid evented component that provides an interface for:
+ * locating processes based on solution "commands".
+ * locating processes based on solution process ids.
+ * locating processes by command.
+ * locating a process by its process id.
+ * emitting an event when a process changes state (`onStateChange`).
+ * emitting an event when a process switches run state (`onRunStateChange`).
+
+Here is how the parts fit together, from the bottom up.
+
+## Nodejs Bridge
+
+The code for the bridge is in the sub-folder "nodeprocesses". In essence, it consists of one JavaScript function to acquire a list of all the processes on the machine, and represent each process as a set of properties.
+
+The first property, "command", is provided by GNOME/Linux's libgtop library and is conceptually the name of the process. Both GNOME/Linux and Mac OS X have a GUI process viewer where they display this property as the "process name". Nodejs has a "process" global object, which represents the Nodejs process itself, that uses a "title" property for the same purpose. Within bash, a simple way to find a process is to list them all using `ps` and then `grep` for the command name, e.g.:
+
+```$ ps aux | grep node```
+
+Unfortunately, the command name is not unique in the sense that more than one process can have the same name. There can be multiple "node" processes runnning simultaneously. The process id, however, is unique. Once a process has been found via its command name, thereafter, one can use its process id to reference that one process.
+
+The full set of properties are:
+
+* command - a string that identifies the command associated with the process.
+* pid - an integer that uniquely identifies the process
+* ppid - the parent process id, unique to the parent.
+* uid - an integer that uniquely identifies the associated user.
+* gid - an integer that uniquely identifies the group the user belongs to.
+* fullPath - the full path to the executable that launched the process.
+* argv - the array of arguments passed to the executable at launch.
+* state - a string representing the current state of the process.
+
+Using orca as an example, the structure is:
+
+```
+{
+ command: 'orca',
+ pid: 7330,
+ ppid: 1315,
+ uid: 1000,
+ gid: 1000,
+ fullPath: '/usr/bin/python3',
+ argv: [ '/usr/bin/python3', '/usr/bin/orca' ],
+ state: 'Sleeping'
+}
+```
+
+The state property can have a number of values, listed below. These are grouped according to how the processReporter component sees a process as "running" vs. "not-running":
+
+* running:
+ * "Running"
+ * "Uninterruptible"
+ * "Sleeping"
+ * "Stopped"
+* not-running:
+ * "Zombie"
+ * "NoSuchProcess"
+
+Note: "NoSuchProcess" is not returned by the nodeprocess add-on, nor the GNOME/Linux process library. If there is no process matching a pid or command, then there is no process information provided. The processReporter component adds the "NoSuchProcess" state as a special case.
+
+### Building Nodejs Bridge
+
+Use the grunt file in the parent "linux" folder:
+
+`$ grunt shell:compileProcesses`
+
+## Process Reporter Bridge
+
+The process reporter bridge is a fluid evented component that makes use of the nodeprocesses node add-on and provides filters for locating processes by command, or by process id. There are also methods for determing if a process has changed state, and, in particular, whether the process has switched between a "running" state vs. a "not-running" state.
+
+Most of the functionality of the bridge is in universal, since that functionality is not specific to GNOME/Linux. The interface to GNOME/Linux is handled by the "gpii.processes.native" (little) component. It has one invoker for acquiring a list of all processes. The "gpii.proccesses" (evented) component, in universal, has all the platform neutral functionality for finding specific processes, and monitoring them.
+
+The files here are:
+
+* processesBridge.js - the native process component. (Note: the platform neutral code is in ".../universal/gpii/node_modules/processReporter/src/").
+* processesBridge_tests.js - unit tests for the component.
+* processReporterBridgeDemo.js - a demo script that shows the evented compontent tracking the status of the "orca" process.
+* ProcessReporter.js - for interfacing with the solutions registry.
+
+There are two experimental features of the evented component that likely require more thought. The first of these is a reliance on ```setInterval()``` to periodically check the status of a given process. The second is a guess as to how the proces reporter could interface with the solutions registry to determine if a solution is running or not.
+
+### Events `onRunStateChange` and `onStateChange`
+
+With respect to periodically checking for a change in process status, processesBridge (in universal) provides two events, each with methods for attaching listeners that react to changes in process status.
+
+The `onRunStateChange` event is fired when a process changes from a "running" state to a "not-running" state. The method `trackRunState()` takes a process information structure, and a handler function as input. It sets up a periodic check of the state of the given process using the global ```setInterval()``` function. If the process status changes from "running" to "not-running" or "not-running" to "running", the given handler function is called. The `trackRunState()` method returns the interval identifier returned by ```setInterval()```; however, the procesReporter provides a method `stopTrackingRunState()` for shutting down the entire tracking mechanics, including clearing the interval.
+
+There is also a `onStateChange` event, and associated methods `trackState()` and `stopTrackingState()` that can be used to periodically check *any* change in state of the given prcoess, and react to the change.
+
+A demonstration of the use of these events is provided in "processesBridgeDemo.js", using the Orca screen reader. The steps to run the demo are:
+
+ 1. `$ node processesBridgeDemo.js`
+ 2. Start a separate terminal session.
+ 3. In this separate terminal, start/stop Orca the way GPII does:
+ * `$ gsettings set org.gnome.desktop.a11y.applications screen-reader-enabled true`
+ * `$ gsettings set org.gnome.desktop.a11y.applications screen-reader-enabled false`
+
+As the screen-reader-enabled setting is toggled between true and false, the processesBridgeDemo will output the new (changed) state of Orca.
+
+### Interface with Solutions Registry
+
+The process reporter also provides a preliminary way for locating processes based on "commands" as provided by the solutions registry. For each solution entry, there is an `"isRunning":` property that lists the function to invoke to find all processes associated with a given command name. For example the following fragment shows the "`isRunning`" property inside the solution entry for the Orca screen reader:
+
+```
+...
+"org.gnome.orca": {
+ "name": "ORCA Screen Reader",
+ "contexts": {
+ "OS": [{
+ "id": "linux",
+ "version": ">=2.6.26"
+ }],
+ "isRunning": [
+ {
+ "type": "gpii.processReporter.find",
+ "command": "orca"
+ }
+ ]
+ },
+...
+```
+
diff --git a/gpii/node_modules/processReporter/index.js b/gpii/node_modules/processReporter/index.js
new file mode 100644
index 0000000..70bdf05
--- /dev/null
+++ b/gpii/node_modules/processReporter/index.js
@@ -0,0 +1,16 @@
+/**
+ * GPII gliptop Process Reporter
+ *
+ * Copyright 2015 Inclusive Design Research Centre, OCAD University
+ *
+ * Licensed under the New BSD license. You may not use this file except in
+ * compliance with this License.
+ *
+ * You may obtain a copy of the License at
+ * https://github.com/gpii/universal/LICENSE.txt
+ */
+
+"use strict";
+
+require("./processReporter.js");
+
diff --git a/gpii/node_modules/processReporter/nodeprocesses/binding.gyp b/gpii/node_modules/processReporter/nodeprocesses/binding.gyp
new file mode 100644
index 0000000..59e0b35
--- /dev/null
+++ b/gpii/node_modules/processReporter/nodeprocesses/binding.gyp
@@ -0,0 +1,12 @@
+{
+ "targets": [
+ {
+ "target_name": "nodeprocesses",
+ "sources": ["nodeprocesses.cc"],
+ "include_dirs": ["
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+using namespace v8;
+using v8::FunctionTemplate;
+using v8::Handle;
+using v8::Object;
+using v8::String;
+using v8::Value;
+using v8::Array;
+using Nan::GetFunction;
+using Nan::New;
+using Nan::Set;
+
+static glibtop* glibtopPtr = NULL;
+
+const char* STATE_RUNNING = "Running";
+const char* STATE_STOPPED = "Stopped";
+const char* STATE_ZOMBIE = "Zombie";
+const char* STATE_UNINTERRUPTIBLE = "Uninterruptible";
+const char* STATE_SLEEPING = "Sleeping";
+
+const guint64 GET_ALL_ARGS = 0;
+const guint64 KERN_PROC_ALL_ARG = 0;
+
+static Handle
+nanEncodeUtf8(const char* string) {
+ return Nan::Encode(string, strlen(string), Nan::UTF8);
+}
+
+
+// The following is based on code in gnome-system-monitor (the "System Monitor"
+// application). The problem is the command name in the glibtop_proc_state
+// struct is truncated. The full name is in the process arguments vector.
+
+static Handle
+get_process_name(const gchar *cmd, const char** argv)
+{
+ char *basename = NULL;
+ Handle procname;
+
+ if (argv) {
+ // look for /usr/bin/very_long_name(argv[0]), or
+ // /usr/bin/interpreter /usr/.../very_long_name(argv[1])
+ // which may have used prctl to alter 'cmd' name.
+ for (int i = 0; i != 2 && argv[i]; i++) {
+ basename = g_path_get_basename(argv[i]);
+ if (g_str_has_prefix(basename, cmd)) {
+ break;
+ }
+ g_free(basename);
+ basename = NULL;
+ }
+ }
+ if (basename != NULL) {
+ procname = nanEncodeUtf8(basename);
+ g_free(basename);
+ }
+ else {
+ procname = nanEncodeUtf8(cmd);
+ }
+ return procname;
+}
+
+static Handle
+format_process_state(guint state)
+{
+ Handle status;
+
+ switch (state)
+ {
+ case GLIBTOP_PROCESS_RUNNING:
+ status = nanEncodeUtf8(STATE_RUNNING);
+ break;
+
+ case GLIBTOP_PROCESS_STOPPED:
+ status = nanEncodeUtf8(STATE_STOPPED);
+ break;
+
+ case GLIBTOP_PROCESS_ZOMBIE:
+ status =nanEncodeUtf8(STATE_ZOMBIE);
+ break;
+
+ case GLIBTOP_PROCESS_UNINTERRUPTIBLE:
+ status = nanEncodeUtf8(STATE_UNINTERRUPTIBLE);
+ break;
+
+ default:
+ status = nanEncodeUtf8(STATE_SLEEPING);
+ break;
+ }
+ return status;
+}
+
+static Handle