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/alsa/alsa_bridge.js b/gpii/node_modules/alsa/alsa_bridge.js index a80f5ac..cea1387 100644 --- a/gpii/node_modules/alsa/alsa_bridge.js +++ b/gpii/node_modules/alsa/alsa_bridge.js @@ -16,7 +16,7 @@ "use strict"; -var fluid = require("universal"); +var fluid = require("gpii-universal"); var gpii = fluid.registerNamespace("gpii"); var alsa = require("./nodealsa/build/Release/nodealsa.node"); diff --git a/gpii/node_modules/alsa/nodealsa/nodealsa_tests.js b/gpii/node_modules/alsa/nodealsa/nodealsa_tests.js index bd6c02c..1d74142 100644 --- a/gpii/node_modules/alsa/nodealsa/nodealsa_tests.js +++ b/gpii/node_modules/alsa/nodealsa/nodealsa_tests.js @@ -16,7 +16,7 @@ "use strict"; -var fluid = require("universal"), +var fluid = require("gpii-universal"), jqUnit = fluid.require("node-jqunit"), alsa = require("./build/Release/nodealsa.node"); diff --git a/gpii/node_modules/alsa/test/alsaSettingsHandlerTests.js b/gpii/node_modules/alsa/test/alsaSettingsHandlerTests.js index 0f93256..d940fec 100644 --- a/gpii/node_modules/alsa/test/alsaSettingsHandlerTests.js +++ b/gpii/node_modules/alsa/test/alsaSettingsHandlerTests.js @@ -16,7 +16,7 @@ "use strict"; -var fluid = require("universal"), +var fluid = require("gpii-universal"), jqUnit = fluid.require("node-jqunit"); require("alsa"); diff --git a/gpii/node_modules/gsettingsBridge/gsettings_bridge.js b/gpii/node_modules/gsettingsBridge/gsettings_bridge.js index 8ce8681..711677c 100644 --- a/gpii/node_modules/gsettingsBridge/gsettings_bridge.js +++ b/gpii/node_modules/gsettingsBridge/gsettings_bridge.js @@ -16,7 +16,7 @@ "use strict"; -var fluid = require("universal"); +var fluid = require("gpii-universal"); var gpii = fluid.registerNamespace("gpii"); var nodeGSettings = require("./nodegsettings/build/Release/nodegsettings.node"); diff --git a/gpii/node_modules/gsettingsBridge/tests/gsettingsTests.js b/gpii/node_modules/gsettingsBridge/tests/gsettingsTests.js index d052953..5928882 100644 --- a/gpii/node_modules/gsettingsBridge/tests/gsettingsTests.js +++ b/gpii/node_modules/gsettingsBridge/tests/gsettingsTests.js @@ -16,7 +16,7 @@ "use strict"; -var fluid = require("universal"), +var fluid = require("gpii-universal"), gpii = fluid.registerNamespace("gpii"), jqUnit = fluid.require("node-jqunit"); diff --git a/gpii/node_modules/orca/orcaSettingsHandler.js b/gpii/node_modules/orca/orcaSettingsHandler.js index d52337c..683d850 100644 --- a/gpii/node_modules/orca/orcaSettingsHandler.js +++ b/gpii/node_modules/orca/orcaSettingsHandler.js @@ -18,7 +18,7 @@ "use strict"; -var fluid = require("universal"); +var fluid = require("gpii-universal"); var gpii = fluid.registerNamespace("gpii"); var fs = require("fs"); var spawn = require("child_process").spawn; diff --git a/gpii/node_modules/orca/test/orcaSettingsHandlerTests.js b/gpii/node_modules/orca/test/orcaSettingsHandlerTests.js index fef1f70..838b97e 100644 --- a/gpii/node_modules/orca/test/orcaSettingsHandlerTests.js +++ b/gpii/node_modules/orca/test/orcaSettingsHandlerTests.js @@ -17,7 +17,7 @@ "use strict"; -var fluid = require("universal"), +var fluid = require("gpii-universal"), jqUnit = fluid.require("node-jqunit"); require("orca"); diff --git a/gpii/node_modules/packagekit/index.js b/gpii/node_modules/packagekit/index.js index e497b7d..dddf29e 100644 --- a/gpii/node_modules/packagekit/index.js +++ b/gpii/node_modules/packagekit/index.js @@ -16,7 +16,7 @@ "use strict"; -var fluid = require("universal"); +var fluid = require("gpii-universal"); require("./packageKitDeviceReporter.js"); diff --git a/gpii/node_modules/packagekit/nodepackagekit/nodepackagekit_test.js b/gpii/node_modules/packagekit/nodepackagekit/nodepackagekit_test.js index df58763..3115834 100644 --- a/gpii/node_modules/packagekit/nodepackagekit/nodepackagekit_test.js +++ b/gpii/node_modules/packagekit/nodepackagekit/nodepackagekit_test.js @@ -14,7 +14,7 @@ https://github.com/gpii/universal/LICENSE.txt "use strict"; -var fluid = require("universal"), +var fluid = require("gpii-universal"), jqUnit = fluid.require("node-jqunit"), gpii = fluid.registerNamespace("gpii"), packagekit = require("./build/Release/nodepackagekit.node"); diff --git a/gpii/node_modules/packagekit/packageKitDeviceReporter.js b/gpii/node_modules/packagekit/packageKitDeviceReporter.js index 3f75954..eda347e 100644 --- a/gpii/node_modules/packagekit/packageKitDeviceReporter.js +++ b/gpii/node_modules/packagekit/packageKitDeviceReporter.js @@ -16,7 +16,7 @@ "use strict"; -var fluid = require("universal"); +var fluid = require("gpii-universal"); var gpii = fluid.registerNamespace("gpii"); var packageKit = require("./nodepackagekit/build/Release/nodepackagekit.node"); diff --git a/gpii/node_modules/packagekit/test/all-tests.js b/gpii/node_modules/packagekit/test/all-tests.js index f5e88b9..d5e54cc 100644 --- a/gpii/node_modules/packagekit/test/all-tests.js +++ b/gpii/node_modules/packagekit/test/all-tests.js @@ -11,7 +11,7 @@ */ "use strict"; -var fluid = require("universal"), +var fluid = require("gpii-universal"), kettle = fluid.require("kettle"); kettle.loadTestingSupport(); diff --git a/gpii/node_modules/packagekit/test/packageKitDeviceReporterTests.js b/gpii/node_modules/packagekit/test/packageKitDeviceReporterTests.js index 079057e..8bb845d 100644 --- a/gpii/node_modules/packagekit/test/packageKitDeviceReporterTests.js +++ b/gpii/node_modules/packagekit/test/packageKitDeviceReporterTests.js @@ -15,7 +15,7 @@ */ "use strict"; -var fluid = require("universal"), +var fluid = require("gpii-universal"), gpii = fluid.registerNamespace("gpii"), jqUnit = fluid.require("node-jqunit"); diff --git a/gpii/node_modules/packagekit/test/packageKitModuleTests.js b/gpii/node_modules/packagekit/test/packageKitModuleTests.js index 5d95122..373ffe9 100644 --- a/gpii/node_modules/packagekit/test/packageKitModuleTests.js +++ b/gpii/node_modules/packagekit/test/packageKitModuleTests.js @@ -15,7 +15,7 @@ */ "use strict"; -var fluid = require("universal"), +var fluid = require("gpii-universal"), jqUnit = fluid.require("node-jqunit"); require("packagekit"); 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 +makeProcInfo(pid_t pid) { + glibtop_proc_state procstate; + glibtop_proc_args procargs; + glibtop_proc_uid procuid; + char** argv = NULL; + + Handle encodedString; + Handle procname; + Handle fullPath = Nan::EmptyString(); + Handle arguments = Nan::New(); + Handle procInfo = Nan::New(); + + glibtop_get_proc_state(&procstate, pid); + glibtop_get_proc_uid(&procuid, pid); + + // Get the argument vector of the process and store the arguments in a v8 + // array. The first element in argv is usually (alas, not always) the + // full path to the executable. Store that as a separate property. + argv = glibtop_get_proc_argv(&procargs, pid, GET_ALL_ARGS); + if (argv != NULL) { + if (argv[0] != NULL) { + fullPath = nanEncodeUtf8(argv[0]); + } + for (int i = 0; argv[i]; i++) { + arguments->Set(i, nanEncodeUtf8(argv[i])); + } + } + procname = get_process_name(procstate.cmd, (const char**) argv); + procInfo->Set(nanEncodeUtf8("command"), procname); + procInfo->Set(nanEncodeUtf8("pid"), Nan::New(pid)); + procInfo->Set(nanEncodeUtf8("ppid"), Nan::New(procuid.ppid)); + procInfo->Set(nanEncodeUtf8("uid"), Nan::New(procuid.uid)); + procInfo->Set(nanEncodeUtf8("gid"), Nan::New(procuid.gid)); + procInfo->Set(nanEncodeUtf8("fullPath"), fullPath); + procInfo->Set(nanEncodeUtf8("argv"), arguments); + procInfo->Set(nanEncodeUtf8("state"), format_process_state(procstate.state)); + + if (argv != NULL) + g_strfreev(argv); + + return procInfo; +} + +/** + * getProcesses: + * + * Returns: Array of currently running processes. + * FIXME: make "Returns:" more explicit. + */ +NAN_METHOD(getProcesses) { + pid_t* pidArray = NULL; + glibtop_proclist procList; + Handle procInfo; + + pidArray = glibtop_get_proclist(&procList, GLIBTOP_KERN_PROC_ALL, KERN_PROC_ALL_ARG); + + Local result = Nan::New(procList.number); + for (unsigned int i=0; iSet(i, procInfo); + } + if (pidArray != NULL) + g_free(pidArray); + + info.GetReturnValue().Set(result); +} + +NAN_MODULE_INIT(init) { + // Safe to call glibtop_init() since it does nothing if already initialized, + // and it returns a pointer to the glibtop structure. + glibtopPtr = glibtop_init(); + + Nan::Set(target, New("getProcesses").ToLocalChecked(), + GetFunction(New(getProcesses)).ToLocalChecked()); +} + +NODE_MODULE(nodeprocesses, init) + diff --git a/gpii/node_modules/processReporter/nodeprocesses/nodeprocesses_test.js b/gpii/node_modules/processReporter/nodeprocesses/nodeprocesses_test.js new file mode 100644 index 0000000..a3df51a --- /dev/null +++ b/gpii/node_modules/processReporter/nodeprocesses/nodeprocesses_test.js @@ -0,0 +1,87 @@ +/*! +GPII Node.js Processes Add-on + +Copyright 2014 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 +*/ + +/*global require*/ + +"use strict"; + +var path = require("path"), + fluid = require("gpii-universal"), + jqUnit = fluid.require("node-jqunit"), + nodeProcesses = require("./build/Release/nodeprocesses.node"); + +var procTests = fluid.registerNamespace("gpii.tests.processes"); + +// Return the process info object that matches the given process id. +// Note that it can return "null" meaning there is no such process running. +procTests.matchProcByPid = function (pid, procArray) { + if (!procArray) { + procArray = nodeProcesses.getProcesses(); + } + return fluid.find(procArray, function (procInfo) { + if (procInfo.pid === pid) { + return procInfo; + } + }, null); +}; + +jqUnit.module("Processes Bridge node add-on module"); + +jqUnit.test( +"Test getProceses() with 'node' (the nodejs process itself)", +function () { + var procInfos = nodeProcesses.getProcesses(); + jqUnit.assertNotEquals( + "Getting all processes", 0, procInfos.length + ); + + // Check for the presence of this nodejs processs itself -- it must + // be in the process list since this code is running inside that + // process. + var nodeProcInfo = procTests.matchProcByPid(process.pid, procInfos); + jqUnit.assertNotNull("Searching for 'node' process", nodeProcInfo); + jqUnit.assertEquals("Node process 'name'", + process.title, nodeProcInfo.command + ); + // TODO: Redundant? This is how it was found. + jqUnit.assertEquals("Node process 'pid'", + process.pid, nodeProcInfo.pid + ); + jqUnit.assertEquals("Node process 'uid'", + process.getuid(), nodeProcInfo.uid + ); + jqUnit.assertEquals("Node process 'gid'", + process.getgid(), nodeProcInfo.gid + ); + jqUnit.assertEquals("Node process 'argv' length'", + process.argv.length, nodeProcInfo.argv.length + ); + jqUnit.assertEquals("Node process status", + "Running", nodeProcInfo.state + ); + + // The "fullPath" property is added by the process node add-on. + // It should match the full path to process.title. + jqUnit.assertEquals("Node process fullPath", + path.resolve(process.title), + path.resolve(nodeProcInfo.fullPath) + ); + + // The order of process.argv is 'node', 'script file', and the rest. + // The order of nodeProcInfo.args is as the "user" typed it, where + // the first argument is the command. Hence, can only test that + // the first argument is the command in both cases. + jqUnit.assertEquals("Node process argv[0]", + path.basename(process.argv[0]), + path.basename(nodeProcInfo.argv[0]) + ); +}); diff --git a/gpii/node_modules/processReporter/package.json b/gpii/node_modules/processReporter/package.json new file mode 100644 index 0000000..a17e890 --- /dev/null +++ b/gpii/node_modules/processReporter/package.json @@ -0,0 +1,19 @@ +{ + "name": "processReporter", + "description": "The Process Reporter module provides information about running solutions on a platform", + "version": "0.1.0", + "author": "Joseph Scheuhammer", + "bugs": "http://wiki.gpii.net/index.php/Main_Page", + "homepage": "http://gpii.net/", + "dependencies": {}, + "licenses": [ + { + "type": "BSD-3-Clause", + "url": "http://www.opensource.org/licenses/BSD-3-Clause" + } + ], + "keywords": ["gpii", "accessibility", "processes", "fluid", "linux"], + "repository": "git://github.com:GPII/linux.git", + "main": "./index.js", + "engines": { "node" : ">=4.2.18" } +} diff --git a/gpii/node_modules/processReporter/processReporter.js b/gpii/node_modules/processReporter/processReporter.js new file mode 100644 index 0000000..0415e31 --- /dev/null +++ b/gpii/node_modules/processReporter/processReporter.js @@ -0,0 +1,67 @@ +/** + * GPII Process Reporter Bridge (Linux -- GLiBTop). + * + * Copyright 2015 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"; + +var fluid = require("gpii-universal"); +var gpii = fluid.registerNamespace("gpii"); +require("./processesBridge.js"); + +var processesBridge = gpii.processes.linux(); + +fluid.require("gsettingsBridge", require); +var gsettings = fluid.registerNamespace("gpii.gsettings"); + +fluid.registerNamespace("gpii.processReporter"); +fluid.defaults("gpii.processReporter.find", { + gradeNames: "fluid.function", + argumentMap: { + command: 0 + } +}); + +fluid.defaults("gpii.processReporter.checkSetting", { + gradeNames: "fluid.function", + argumentMap: { + schema: 0, + setting: 1, + value: 2 + } +}); + +// Search for the process using its command name. Returns a boolean indicating +// if the process is running. +gpii.processReporter.find = function (commandName) { + var running = false; + var procInfos = processesBridge.findSolutionsByCommands([commandName]); + var theProcess = fluid.find(procInfos, function (aProcInfo) { + if (aProcInfo.uid === process.getuid()) { + return aProcInfo; + } + }, null); + + if (theProcess !== null) { + running = processesBridge.isRunning(theProcess.state); + } + return running; +}; + +// Check if the given GSetting is set. Returns a boolean indicating whether the +// setting is set. +gpii.processReporter.checkSetting = function (schema, setting, value) { + var actualValue = undefined; + if (schema && setting) { + actualValue = gsettings.getSingleKey(schema, setting); + } + return value === actualValue; +}; + diff --git a/gpii/node_modules/processReporter/processesBridge.js b/gpii/node_modules/processReporter/processesBridge.js new file mode 100644 index 0000000..f17bb84 --- /dev/null +++ b/gpii/node_modules/processReporter/processesBridge.js @@ -0,0 +1,36 @@ +/*! +GPII Process Reporter processes bridge -- gpii.processes. + +Copyright 2014-2017 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 +*/ + +/*global require */ + +"use strict"; + +var fluid = require("gpii-universal"); +var gpii = fluid.registerNamespace("gpii"); +var nodeProcesses = require("./nodeprocesses/build/Release/nodeprocesses.node"); + +var gpii = fluid.registerNamespace("gpii"); + +fluid.defaults("gpii.processes.linux", { + gradeNames: ["gpii.processes"], + invokers: { + getProcessList: { + funcName: "gpii.processes.linux.getProcessList", + args: [] + } + } +}); + +// Return a list of processes -- a snapshot of the current processes. +gpii.processes.linux.getProcessList = function () { + return nodeProcesses.getProcesses(); +}; diff --git a/gpii/node_modules/processReporter/test/all-tests.js b/gpii/node_modules/processReporter/test/all-tests.js new file mode 100644 index 0000000..4474763 --- /dev/null +++ b/gpii/node_modules/processReporter/test/all-tests.js @@ -0,0 +1,28 @@ +/** + * GPII PackageKit Device Reporter Tests + * + * 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"; + +var fluid = require("gpii-universal"), + kettle = fluid.require("kettle"); + +kettle.loadTestingSupport(); + +var testIncludes = [ + "./processesBridge_tests.js", + "./processReporterModuleTests.js" +]; + +var tests = []; + +fluid.each(testIncludes, function (path) { + tests = tests.concat(fluid.require(path, require)); +}); diff --git a/gpii/node_modules/processReporter/test/processReporterModuleTests.js b/gpii/node_modules/processReporter/test/processReporterModuleTests.js new file mode 100644 index 0000000..6d05df4 --- /dev/null +++ b/gpii/node_modules/processReporter/test/processReporterModuleTests.js @@ -0,0 +1,76 @@ +/** + * GPII Process Reporter Tests + * + * 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"; + +var fluid = require("gpii-universal"), + jqUnit = fluid.require("node-jqunit"); + +require("processReporter"); + +var gpii = fluid.registerNamespace("gpii"); +var processReporter = fluid.registerNamespace("gpii.processReporter"); + +fluid.require("gsettingsBridge", require); +var gsettings = fluid.registerNamespace("gpii.gsettings"); + +jqUnit.module("GPII Linux ProcessReporter Module"); + +jqUnit.test("Running tests for Linux/GNOME Process Reporter", function () { + jqUnit.expect(8); + + // Check that the bridge is loaded and required methods are available + // + var methods = ["find", "checkSetting"]; + for (var method in methods) { + jqUnit.assertTrue("Checking availability of method '" + method + "'", + (methods[method] in processReporter)); + } + // This is running inside 'node' itself, so the uid matches. + jqUnit.assertTrue("Checking run-status of process 'node'", + gpii.processReporter.find("node")); + + // Unlikely there is ever a process named "T6y7u8i9C". + jqUnit.assertFalse("Checking run-status of process 'T6y7u8i9'", + gpii.processReporter.find("T6y7u8i9C")); + + // There is likely a 'gdm' process, but its uid is not us. + jqUnit.assertFalse("Checking run-status of un-owned 'gdm' process", + gpii.processReporter.find("gdm")); + + // Unknown what the status of a given setting is, but checkSetting() should + // always return a boolean value. + var aValue = gsettings.getSingleKey( + "org.gnome.desktop.a11y.applications", "screen-magnifier-enabled" + ); + jqUnit.assertTrue("Checking retrieval of a boolean setting", + gpii.processReporter.checkSetting( + "org.gnome.desktop.a11y.applications", + "screen-magnifier-enabled", + aValue) + ); + aValue = gsettings.getSingleKey( + "org.gnome.desktop.a11y.magnifier", "mag-factor" + ); + jqUnit.assertTrue("Checking retrieval of a numeric setting", + gpii.processReporter.checkSetting( + "org.gnome.desktop.a11y.magnifier", + "mag-factor", + aValue) + ); + aValue = "foobar"; + jqUnit.assertFalse("Checking retrieval of a non-existent setting", + gpii.processReporter.checkSetting( + "org.gnome.desktop.a11y.applications", + "screen-magnifier-enabled", + aValue) + ); +}); diff --git a/gpii/node_modules/processReporter/test/processesBridge_tests.js b/gpii/node_modules/processReporter/test/processesBridge_tests.js new file mode 100644 index 0000000..9d3e1fc --- /dev/null +++ b/gpii/node_modules/processReporter/test/processesBridge_tests.js @@ -0,0 +1,205 @@ +/*! +GPII Node.js Processes Bridge Unit Tests + +Copyright 2014 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 +*/ + +/*global require*/ + +"use strict"; + +var path = require("path"), + spawn = require("child_process").spawn, + fluid = require("gpii-universal"), + jqUnit = fluid.require("node-jqunit"); + +require("../processesBridge.js"); + +var gpii = fluid.registerNamespace("gpii"); +var processesBridge = gpii.processes.linux(); + +jqUnit.module("Processes Bridge node add-on module"); +jqUnit.test( + "Test getProceses()/findProcessByPid() with the nodejs process itself", + function () { + var procInfos = processesBridge.getProcessList(); + jqUnit.assertNotEquals( + "Listing all processes", 0, procInfos.length + ); + // Check for the presence of this nodejs processs itself -- it must + // be in the process list since this code is running inside that + // process. + var nodeProc = processesBridge.findProcessByPid(process.pid, procInfos); + jqUnit.assertNotNull("Searching for 'node' process", nodeProc); + } +); + +jqUnit.test( + "Test findProcessByPid() with non-running process id", + function () { + jqUnit.assertNull( + "Search negative process id value", processesBridge.findProcessByPid(-1) + ); + } +); + +jqUnit.test( + "Test findProcessByPid() against nodejs's own process object.", + function () { + var nodeProcInfo = processesBridge.findProcessByPid(process.pid); + jqUnit.assertEquals("Node process 'name'", + process.title, nodeProcInfo.command); + + // Redundant? This is how it was found. + jqUnit.assertEquals("Node process 'pid'", + process.pid, nodeProcInfo.pid); + + jqUnit.assertEquals("Node process 'uid'", + process.getuid(), nodeProcInfo.uid); + + jqUnit.assertEquals("Node process 'gid'", + process.getgid(), nodeProcInfo.gid); + + jqUnit.assertEquals("Node process 'argv' length'", + process.argv.length, nodeProcInfo.argv.length); + + jqUnit.assertEquals("Node process status", + "Running", nodeProcInfo.state); + + // The "fullPath" property is added by the process node add-on. + // It should match the full path to process.title. + jqUnit.assertEquals("Node process fullPath", + path.resolve(process.title), + path.resolve(nodeProcInfo.fullPath) + ); + + // The order of process.argv and nodeProcInfo.argv is not + // necessarily the same, nor are the number of arguments the same. + // Only the first argument of vectors match as the name of the + // process (here "node"). Hence, can only test that the first + // argument is the command in both cases. + jqUnit.assertEquals("Node process argv[0]", + path.basename(process.argv[0]), + path.basename(nodeProcInfo.argv[0]) + ); + } +); + +jqUnit.test( + "Test findProcessesByCmd()/findFirstProcessByCmd() with nodejs itself", + function () { + var nodeProcInfos = processesBridge.findProcessesByCommand("node"); + jqUnit.assertNotEquals( + "Getting all 'node' processes", 0, nodeProcInfos.length + ); + nodeProcInfos.forEach(function (aProcInfo) { + jqUnit.assertEquals( + "Node commmand name", "node", aProcInfo.command + ); + }); + var procInfo = processesBridge.findFirstProcessByCommand("node"); + jqUnit.assertNotNull( + "Looking for first 'node' processes", procInfo); + jqUnit.assertEquals("Node commmand name", "node", procInfo.command); + } +); + +jqUnit.test( + "Test initProcInfoNotRunning()", + function () { + var notRunning = processesBridge.initProcInfoNotRunning("grep"); + jqUnit.assertEquals("Command name", notRunning.command, "grep"); + jqUnit.assertEquals("Negative process id", notRunning.pid, -1); + jqUnit.assertEquals( + "'NoSuchProcess' state", notRunning.state, "NoSuchProcess" + ); + jqUnit.assertNull( + "Search negative process id value", + processesBridge.findProcessByPid(notRunning.pid) + ); + } +); + +jqUnit.test( + "Test isRunning() with nodejs itself, and nonexistent process", + function () { + var procInfo = processesBridge.findProcessByPid(process.pid); + jqUnit.assertNotNull("Searching for 'node' process", procInfo); + jqUnit.assertTrue( + "Check nodejs is running", + processesBridge.isRunning(procInfo.state) + ); + procInfo = processesBridge.initProcInfoNotRunning("grep"); + jqUnit.assertFalse( + "Check nonexistent process running", + processesBridge.isRunning(procInfo) + ); + } +); + +jqUnit.test( + "Test updateProcInfo() against non-changing process", + function () { + var procInfo = processesBridge.findProcessByPid(process.pid); + jqUnit.assertNotNull("Looking for 'node' processes", procInfo); + var newProcInfo = processesBridge.updateProcInfo(procInfo); + jqUnit.assertDeepEq( + "Check change in process info", procInfo, newProcInfo + ); + } +); + +jqUnit.test( + "Test updateProcInfo() against changing process", + function () { + var grep = spawn("grep", ["ssh"]); + var grepInfo = processesBridge.findProcessByPid(grep.pid); + jqUnit.assertNotNull("Search 'grep' process", grepInfo); + jqUnit.assertTrue("Stop grep", grep.kill("SIGHUP")); + var newGrepInfo = processesBridge.updateProcInfo(grepInfo); + jqUnit.assertNotEquals( + "Update process state", newGrepInfo.state, grepInfo.state + ); + } +); + +jqUnit.test( + "Test findSolutionsByCommands()", + function () { + // Node is running. Add a running cat. No such command as T6y7u8i9. + var cat = spawn("cat"); + var solutions = ["node", "cat", "T6y7u8i9"]; + var procInfos = processesBridge.findSolutionsByCommands(solutions); + jqUnit.assertTrue("Node and cat processes", procInfos.length >= 2); + procInfos.forEach(function (item) { + var isNode = item.command === "node"; + var isCat = item.command === "cat"; + jqUnit.assertTrue("Process name node nor cat", isNode || isCat); + }); + cat.kill("SIGHUP"); + } +); + +jqUnit.test( + "Test findSolutionsByPids()", + function () { + // Node is running. Add a running cat process. + var cat = spawn("cat"); + var pids = [process.pid, cat.pid]; + var procInfos = processesBridge.findSolutionsByPids(pids); + jqUnit.assertEquals("Node and cat processes", 2, procInfos.length); + procInfos.forEach(function (item) { + var isNode = item.pid === process.pid; + var isCat = item.pid === cat.pid; + jqUnit.assertTrue("Process pid node nor cat", isNode || isCat); + }); + cat.kill("SIGHUP"); + } +); + diff --git a/gpii/node_modules/xrandr/nodexrandr/nodexrandr_tests.js b/gpii/node_modules/xrandr/nodexrandr/nodexrandr_tests.js index 56358be..55068af 100644 --- a/gpii/node_modules/xrandr/nodexrandr/nodexrandr_tests.js +++ b/gpii/node_modules/xrandr/nodexrandr/nodexrandr_tests.js @@ -16,7 +16,7 @@ "use strict"; -var fluid = require("universal"), +var fluid = require("gpii-universal"), jqUnit = fluid.require("node-jqunit"), xrandr = require("./build/Release/nodexrandr.node"); diff --git a/gpii/node_modules/xrandr/test/xrandrSettingsHandlerTests.js b/gpii/node_modules/xrandr/test/xrandrSettingsHandlerTests.js index 936929b..34c0810 100644 --- a/gpii/node_modules/xrandr/test/xrandrSettingsHandlerTests.js +++ b/gpii/node_modules/xrandr/test/xrandrSettingsHandlerTests.js @@ -16,7 +16,7 @@ "use strict"; -var fluid = require("universal"), +var fluid = require("gpii-universal"), jqUnit = fluid.require("node-jqunit"); require("xrandr"); diff --git a/gpii/node_modules/xrandr/xrandr_bridge.js b/gpii/node_modules/xrandr/xrandr_bridge.js index b54442f..4154afc 100644 --- a/gpii/node_modules/xrandr/xrandr_bridge.js +++ b/gpii/node_modules/xrandr/xrandr_bridge.js @@ -17,7 +17,7 @@ "use strict"; -var fluid = require("universal"); +var fluid = require("gpii-universal"); var gpii = fluid.registerNamespace("gpii"); var xrandr = require("./nodexrandr/build/Release/nodexrandr.node"); diff --git a/index.js b/index.js index cb2ad0b..f2c5113 100644 --- a/index.js +++ b/index.js @@ -17,7 +17,7 @@ "use strict"; -var fluid = require("universal"); +var fluid = require("gpii-universal"); fluid.module.register("gpii-linux", __dirname, require); @@ -37,3 +37,7 @@ require("./gpii/node_modules/xrandr"); // Device Reporters require("./gpii/node_modules/packagekit"); + +// Process reporters +// +require("./gpii/node_modules/processReporter"); diff --git a/package.json b/package.json index ce76527..b981b48 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "bugs": "http://issues.gpii.net/browse/GPII", "homepage": "http://gpii.net/", "dependencies": { - "universal": "GPII/universal#1a3434d970cb484956eb18310683bdfe473123d6" + "gpii-universal": "0.3.0-dev.20180221T093422Z.50833c1" }, "devDependencies": { "grunt": "1.0.1", @@ -15,7 +15,7 @@ "grunt-shell": "1.3.0", "nan": "2.4.0" }, - "license" : "BSD-3-Clause", + "license": "BSD-3-Clause", "keywords": [ "gpii", "accessibility", @@ -29,6 +29,6 @@ "repository": "git://github.com/GPII/linux.git", "main": "./gpii/index.js", "engines": { - "node": ">=4.2.1" + "node": ">=4.2.18" } } diff --git a/provisioning/vars.yml b/provisioning/vars.yml index d3fe76c..9b936e7 100644 --- a/provisioning/vars.yml +++ b/provisioning/vars.yml @@ -35,6 +35,7 @@ nodejs_app_rpm_packages: - libstdc++-devel - libuv - libuv-devel + - libgtop2-devel nodejs_app_npm_packages: - node-gyp diff --git a/tests/AcceptanceTests.js b/tests/AcceptanceTests.js index d1ca5f8..19baac2 100644 --- a/tests/AcceptanceTests.js +++ b/tests/AcceptanceTests.js @@ -16,7 +16,7 @@ Seventh Framework Programme (FP7/2007-2013) under grant agreement no. 289016. "use strict"; -var fluid = require("universal"), +var fluid = require("gpii-universal"), gpii = fluid.registerNamespace("gpii"); gpii.loadTestingSupport(); @@ -25,7 +25,7 @@ fluid.registerNamespace("gpii.acceptanceTesting.linux"); require("../index.js"); -var baseDir = fluid.module.resolvePath("%universal/tests/"); -var linuxFiles = fluid.require("%universal/tests/platform/index-linux.js"); +var baseDir = fluid.module.resolvePath("%gpii-universal/tests/"); +var linuxFiles = fluid.require("%gpii-universal/tests/platform/index-linux.js"); gpii.test.runSuitesWithFiltering(linuxFiles, baseDir, ["gpii.test.acceptance.testCaseHolder"]); diff --git a/tests/UnitTests.sh b/tests/UnitTests.sh index d90fc1f..9f10a69 100755 --- a/tests/UnitTests.sh +++ b/tests/UnitTests.sh @@ -41,6 +41,13 @@ cd ../nodepackagekit node nodepackagekit_test.js popd +pushd . +cd ../gpii/node_modules/processReporter/test/ +node ./all-tests.js +cd ../nodeprocesses +node nodeprocesses_test.js +popd + # These XRANDR tests crash out on my system (AMB - Fedora 19 64-bit in VMWare Workstation 10.0.1 on Windows 7 64-bit) node ../gpii/node_modules/xrandr/nodexrandr/nodexrandr_tests.js node ../gpii/node_modules/xrandr/test/xrandrSettingsHandlerTests.js