├── .gitignore
├── LICENSE
├── README.md
├── index.js
└── package.json
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | npm-debug.log
3 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (C) 2012 ProxV, Inc.
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8 |
9 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | node-xvfb: easily start and stop an X Virtual Frame Buffer from your node apps.
2 | -----
3 |
4 | ### Usage
5 |
6 | ```javascript
7 | var Xvfb = require('xvfb');
8 | var xvfb = new Xvfb();
9 | xvfb.startSync();
10 |
11 | // code that uses the virtual frame buffer here
12 |
13 | xvfb.stopSync();
14 | // the Xvfb is stopped
15 | ```
16 |
17 | or:
18 |
19 |
20 | ```javascript
21 | var Xvfb = require('xvfb');
22 | var xvfb = new Xvfb();
23 | xvfb.start(function(err, xvfbProcess) {
24 | // code that uses the virtual frame buffer here
25 | xvfb.stop(function(err) {
26 | // the Xvfb is stopped
27 | });
28 | });
29 | ```
30 |
31 | The Xvfb constructor takes four options:
32 |
33 | * displayNum
- the X display to use, defaults to the lowest unused display number >= 99 if reuse
is false or 99 if reuse
is true.
34 | * reuse
- whether to reuse an existing Xvfb instance if it already exists on the X display referenced by displayNum.
35 | * timeout
- number of milliseconds to wait when starting Xvfb before assuming it failed to start, defaults to 500.
36 | * silent
- don't pipe Xvfb stderr to the process's stderr.
37 |
38 | ### Thanks to
39 |
40 | * [kesla](https://github.com/kesla) for https://github.com/kesla/node-headless
41 | * [leonid-shevtsov](https://github.com/leonid-shevtsov) for https://github.com/leonid-shevtsov/headless
42 |
43 | both of which served as inspiration for this package.
44 |
45 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs');
2 | var path = require('path');
3 | var spawn = require('child_process').spawn;
4 | var usleep = require('sleep').usleep;
5 | fs.exists = fs.exists || path.exists;
6 | fs.existsSync = fs.existsSync || path.existsSync;
7 |
8 |
9 | function Xvfb(options) {
10 | options = options || {};
11 | this._display = (options.displayNum ? ':' + options.displayNum : null);
12 | this._reuse = options.reuse;
13 | this._timeout = options.timeout || 500;
14 | this._silent = options.silent;
15 | }
16 |
17 | Xvfb.prototype = {
18 | start: function(cb) {
19 | if (!this._process) {
20 | var lockFile = this._lockFile();
21 |
22 | this._setDisplayEnvVariable();
23 |
24 | fs.exists(lockFile, function(exists) {
25 | try {
26 | this._spawnProcess(exists);
27 | } catch (e) {
28 | return cb && cb(e);
29 | }
30 |
31 | var totalTime = 0;
32 | (function checkIfStarted() {
33 | fs.exists(lockFile, function(exists) {
34 | if (exists) {
35 | return cb && cb(null, this._process);
36 | } else {
37 | totalTime += 10;
38 | if (totalTime > this._timeout) {
39 | return cb && cb(new Error('Could not start Xvfb.'));
40 | } else {
41 | setTimeout(checkIfStarted.bind(this), 10);
42 | }
43 | }
44 | });
45 | }).bind(this)();
46 | }.bind(this));
47 | }
48 | },
49 |
50 | startSync: function() {
51 | if (!this._process) {
52 | var lockFile = this._lockFile();
53 |
54 | this._setDisplayEnvVariable();
55 | this._spawnProcess(fs.existsSync(lockFile));
56 |
57 | var totalTime = 0;
58 | while (!fs.existsSync(lockFile)) {
59 | if (totalTime > this._timeout) {
60 | throw new Error('Could not start Xvfb.');
61 | }
62 | usleep(10000);
63 | totalTime += 10;
64 | }
65 | }
66 |
67 | return this._process;
68 | },
69 |
70 | stop: function(cb) {
71 | if (this._process) {
72 | this._killProcess();
73 | this._restoreDisplayEnvVariable();
74 |
75 | var lockFile = this._lockFile();
76 | var totalTime = 0;
77 | (function checkIfStopped() {
78 | fs.exists(lockFile, function(exists) {
79 | if (!exists) {
80 | return cb && cb(null, this._process);
81 | } else {
82 | totalTime += 10;
83 | if (totalTime > this._timeout) {
84 | return cb && cb(new Error('Could not stop Xvfb.'));
85 | } else {
86 | setTimeout(checkIfStopped.bind(this), 10);
87 | }
88 | }
89 | });
90 | }).bind(this)();
91 | } else {
92 | return cb && cb(null);
93 | }
94 | },
95 |
96 | stopSync: function() {
97 | if (this._process) {
98 | this._killProcess();
99 | this._restoreDisplayEnvVariable();
100 |
101 | var lockFile = this._lockFile();
102 | var totalTime = 0;
103 | while (fs.existsSync(lockFile)) {
104 | if (totalTime > this._timeout) {
105 | throw new Error('Could not stop Xvfb.');
106 | }
107 | usleep(10000);
108 | totalTime += 10;
109 | }
110 | }
111 | },
112 |
113 | display: function() {
114 | if (!this._display) {
115 | var displayNum = 98;
116 | var lockFile;
117 | do {
118 | displayNum++;
119 | lockFile = this._lockFile(displayNum);
120 | } while (!this._reuse && path.existsSync(lockFile));
121 | this._display = ':' + displayNum;
122 | }
123 | return this._display;
124 | },
125 |
126 | _setDisplayEnvVariable: function() {
127 | this._oldDisplay = process.env.DISPLAY;
128 | process.env.DISPLAY = this.display();
129 | },
130 |
131 | _restoreDisplayEnvVariable: function() {
132 | process.env.DISPLAY = this._oldDisplay;
133 | },
134 |
135 | _spawnProcess: function(lockFileExists) {
136 | var display = this.display();
137 | if (lockFileExists) {
138 | if (!this._reuse) {
139 | throw new Error('Display ' + display + ' is already in use and the "reuse" option is false.');
140 | }
141 | } else {
142 | this._process = spawn('Xvfb', [ display ]);
143 | this._process.stderr.on('data', function(data) {
144 | if (!this._silent) {
145 | process.stderr.write(data);
146 | }
147 | }.bind(this));
148 | }
149 | },
150 |
151 | _killProcess: function() {
152 | this._process.kill();
153 | this._process = null;
154 | },
155 |
156 | _lockFile: function(displayNum) {
157 | displayNum = displayNum || this.display().toString().replace(/^:/, '');
158 | return '/tmp/.X' + displayNum + '-lock';
159 | }
160 | }
161 |
162 | module.exports = Xvfb;
163 |
164 |
165 | if (require.main === module) {
166 | var assert = require('assert');
167 | var xvfb = new Xvfb({ displayNum: 88 });
168 | xvfb.startSync();
169 | console.error('started sync');
170 | xvfb.stopSync();
171 | console.error('stopped sync');
172 | xvfb.start(function(err) {
173 | assert.equal(err, null);
174 | console.error('started async');
175 | xvfb.stop(function(err) {
176 | assert.equal(err, null);
177 | console.error('stopped async');
178 | xvfb.start(function(err) {
179 | assert.equal(err, null);
180 | console.error('started async');
181 | xvfb.stopSync();
182 | console.error('stopped sync');
183 | xvfb.startSync();
184 | console.error('started sync');
185 | xvfb.stop(function(err) {
186 | assert.equal(err, null);
187 | console.error('stopped async');
188 | });
189 | });
190 | });
191 | });
192 | }
193 |
194 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "ProxV, Inc. (http://proxv.com)",
3 | "name": "xvfb",
4 | "description": "Easily start and stop an X Virtual Frame Buffer from your node apps.",
5 | "version": "0.2.0",
6 | "repository": {
7 | "url": "https://github.com/proxv/node-xvfb.git"
8 | },
9 | "dependencies": {
10 | "sleep": "1.1.x"
11 | },
12 | "devDependencies": {},
13 | "optionalDependencies": {},
14 | "engines": {
15 | "node": "*"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------