47 Commits

Author SHA1 Message Date
Thomas Ruoff
06855cee91 Update README.md 2016-03-17 10:28:06 +01:00
Thomas Ruoff
c71c1629e2 Merge pull request #4 from tomru/increase-coverage
test for errors, use profiles
2016-03-14 13:30:04 +01:00
Thomas Ruoff
5d945c8f66 use es6 function everywhere 2016-03-14 13:19:22 +01:00
Thomas Ruoff
52ba27e092 test for errors, use profiles 2016-03-14 13:16:54 +01:00
Thomas Ruoff
272441dd70 bump version 2016-03-14 09:47:00 +01:00
Thomas Ruoff
fe99d75cb4 Merge pull request #3 from tomru/callbacks
switch to good old cbs
2016-03-13 15:00:31 +01:00
Thomas Ruoff
90ad436396 fix tests for switch to callbacks 2016-03-13 14:56:33 +01:00
Thomas Ruoff
a64a14419b switch to callbacks 2016-03-11 15:07:19 +01:00
Thomas Ruoff
ef0b8de4e1 add .lvimrc to gitignore 2016-03-11 15:07:19 +01:00
Thomas Ruoff
03f11a88d5 more cli tests 2016-03-11 15:07:19 +01:00
Thomas Ruoff
a82891c9e8 flatten promise chain
Got problems making test work with the old way - sigh
2016-03-11 15:07:19 +01:00
Thomas Ruoff
e881214463 add tests for usage and listing devices 2016-03-11 15:07:19 +01:00
Thomas Ruoff
f6f0de0e04 remove unneeded proces.exit calls 2016-03-11 15:07:19 +01:00
Thomas Ruoff
9d25ebfc33 fix branch in coverall link 2016-03-11 14:37:27 +01:00
Thomas Ruoff
adaa1ed68a fix branch in coverall link 2016-03-11 14:36:12 +01:00
Thomas Ruoff
c1c0f75c0a Update README.md 2016-03-06 22:43:03 +01:00
Thomas Ruoff
41e85ebb68 Merge pull request #1 from tomru/coverage
add coveralls
2016-03-06 22:41:23 +01:00
Thomas Ruoff
372eaab45e add coveralls 2016-03-06 22:38:09 +01:00
Thomas Ruoff
fa06e7914b Update README.md 2016-03-06 19:16:41 +01:00
Thomas Ruoff
412f26a9dd Update README.md 2016-03-06 19:15:56 +01:00
Thomas Ruoff
0adc89d2d9 add travis config 2016-03-06 19:09:47 +01:00
Thomas Ruoff
c1e651245e beautiful blank line 2016-03-06 19:06:48 +01:00
Thomas Ruoff
77c1f3c674 add istanbul coverage 2016-03-06 18:54:59 +01:00
Thomas Ruoff
15faee38c9 complete tests for swm.js 2016-03-06 11:11:49 +01:00
Thomas Ruoff
67695abfab only log error for explicitly slected monitors that are not connected 2016-03-06 11:11:34 +01:00
Thomas Ruoff
128724bbc8 simplify usage of exec 2016-03-06 11:11:01 +01:00
Thomas Ruoff
029b12332a add script for mocha watch 2016-03-06 11:10:24 +01:00
Thomas Ruoff
067471dbc5 fix main entry 2016-03-06 01:00:25 +01:00
Thomas Ruoff
85cb7ba3f8 tests for getDevices 2016-03-06 00:59:18 +01:00
Thomas Ruoff
dc0552c10d bump version for profiles 2016-03-03 15:00:12 +01:00
Thomas Ruoff
196b9cb8c8 update readme and usage 2016-03-02 00:15:40 +01:00
Thomas Ruoff
e3054967b4 add support for predefined profiles 2016-03-01 23:38:24 +01:00
Thomas Ruoff
3b0ee78cf4 ignore missing config file, but throw other errors 2016-03-01 23:37:55 +01:00
Thomas Ruoff
a2a47cdf8d bump version 2016-03-01 13:55:16 +01:00
Thomas Ruoff
242a12d12a add optional config for postCmd 2016-03-01 13:54:41 +01:00
Thomas Ruoff
511b972c22 add packed file to gitignore 2016-03-01 12:18:07 +01:00
Thomas Ruoff
7d391334f1 refactored xrandr options generation 2016-03-01 12:16:15 +01:00
Thomas Ruoff
31711c7191 fix selecting a single device 2016-03-01 11:24:39 +01:00
Thomas Ruoff
0cff05484f add list support 2016-03-01 11:19:30 +01:00
Thomas Ruoff
d2e0e172f9 split cli and helper, add bin entry to package.json 2016-01-18 10:07:57 +01:00
Thomas Ruoff
610fd07bec remove obsolete loggin statement 2016-01-18 09:27:43 +01:00
Thomas Ruoff
985d394872 refactored to support --right-of 2016-01-18 00:05:23 +01:00
Thomas Ruoff
04a8bf17eb add TODO 2016-01-15 00:46:46 +01:00
Thomas Ruoff
d452fefc9c switch to node.js 2016-01-15 00:45:52 +01:00
Thomas Ruoff
6dca960958 fix that all montiors are added to be turned off 2015-02-13 13:08:43 +01:00
Thomas Ruoff
cbe0c518d8 can pass a single device now 2015-02-13 11:31:54 +01:00
Thomas Ruoff
919735bf5c ops, fix my email address 2014-07-28 23:05:46 +02:00
15 changed files with 775 additions and 45 deletions

14
.editorconfig Normal file
View File

@@ -0,0 +1,14 @@
# This file is for unifying the coding style for different editors and IDEs
# editorconfig.org
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
trim_trailing_whitespace = true
[**.{js,json,md}]
insert_final_newline = true

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
node_modules/
switchmon-*.tgz
coverage
.lvimrc

22
.jshintrc Normal file
View File

@@ -0,0 +1,22 @@
{
"node": true,
"browser": false,
"esnext": true,
"bitwise": true,
"camelcase": true,
"curly": true,
"eqeqeq": true,
"immed": true,
"indent": 4,
"latedef": true,
"newcap": true,
"noarg": true,
"quotmark": "single",
"regexp": true,
"undef": true,
"unused": true,
"strict": true,
"trailing": true,
"smarttabs": true,
"globals" : {}
}

2
.npmignore Normal file
View File

@@ -0,0 +1,2 @@
.editorconfig
.jshintrc

4
.travis.yml Normal file
View File

@@ -0,0 +1,4 @@
language: node_js
node_js:
- "5"
install: npm install

View File

@@ -1,14 +1,58 @@
[![Build Status](https://travis-ci.org/tomru/switchmon.svg?branch=master)](https://travis-ci.org/tomru/switchmon) [![Coverage Status](https://coveralls.io/repos/github/tomru/switchmon/badge.svg?branch=master)](https://coveralls.io/github/tomru/switchmon?branch=master)
# SWM - Switch Monitors # SWM - Switch Monitors
Simple helper for turning on/off connected/disconnected monitors with `xrandr`. Simple helper for turning on/off connected/disconnected monitors with `xrandr`.
It uses `xrandr` to detect on which connectors there are monitors connected. It ## Synopsis
then runs `xrandr` and sets all disconnected monitors to `off` and all connected `swm`
monitors to `on`.
Monitors will be turned on with xrandr `--auto` option which tries to detect `swm [monitor-1..montior-n]`
the optimal video setting.
If there are multiple monitors connected, each monitor is placed to the right `swm --profile external`
of the previous monitor. This order is currently driven as xrandr lists the
connectors. `swm --list`
## Description
To turn on/off connected/disconnected monitors.
If no monitors are specified all connected monitors will be turned on and
placed from left to right in alphabetical order of their name.
If monitors `monitor-1..monitor-n` are specified these monitors will be turned
on and place them from left to right in the order given.
* `--profile profilename` or `-p profilename`
If a profile is specified, the configured monitors will be turned on.
* `--postCmd "some cmd"`
A post command is executed after switching the monitors. This is usefull to
tell your window manager to re-detect monitors, e.g. for herbstluftwm
`herbstclient reload`.
* `-l` or `swm --list`
List all devices with the connectivity status.
## Configuration
The configuration can be placed in `$XDG_CONFIG_HOME/switchmon/config.json` in
the form of
```
{
"postCmd": "some command",
"profiles": {
"internal": ["LVDS1"],
"external": ["HDMI1"],
"dual": ["LVDS1", "HDMI1"]
}
}
```
## Requirements
Node.js > v4 on your PATH.

52
cli.js Executable file
View File

@@ -0,0 +1,52 @@
#!/usr/bin/env node
'use strict';
const argv = require('minimist')(process.argv.slice(2));
const swm = require('./swm.js');
const config = require('./config.js');
const usage = require('./usage.js');
const postCmd = argv.postCmd || config.postCmd;
const profile = argv.profile || argv.p;
function connectionStatus(device) {
return device.connected ? 'Connected' : 'Disconnected';
}
if (argv.help || argv.h) {
console.log(usage);
return;
} else if (argv.list || argv.l) {
const devices = swm.getDevices((err, devices) => {
if (err) {
throw new Error(err);
}
console.log('Detected devices:\n');
Object.keys(devices)
.sort(key => !devices[key].connected)
.forEach(key => console.log(key + ':', connectionStatus(devices[key])));
});
} else {
let selectedMonitors = argv._;
if (profile) {
if (!config.profiles[profile]) {
console.error('profile', profile, 'not found in config');
process.exit(1);
}
selectedMonitors = config.profiles[profile];
console.log('Using profile', profile);
}
console.log('Switching on', selectedMonitors.length ? selectedMonitors : 'all connected monitors');
swm.getDevices((err, devices) => {
if (err) {
throw new Error(err);
}
const xrandrOptions = swm.generateXrandrOptions(selectedMonitors, devices);
swm.switchDevices(xrandrOptions);
swm.executePostCmd(postCmd);
});
}

21
config.js Normal file
View File

@@ -0,0 +1,21 @@
'use strict';
const configPath = require('xdg').basedir.configPath('switchmon/config.json');
const defaults = {
postCmd: undefined,
profiles: {},
};
let config = Object.assign({}, defaults);
try {
config = Object.assign(config, require(configPath));
} catch(err) {
if (err.code !== 'MODULE_NOT_FOUND') {
throw err;
}
// No config found in "${configPath}". Using defaults...
}
module.exports = config;

40
package.json Normal file
View File

@@ -0,0 +1,40 @@
{
"name": "switchmon",
"version": "1.2.0",
"description": "Simple helper for turning on/off connected/disconnected monitors with xrandr",
"main": "cli.js",
"scripts": {
"test": "istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- -R spec && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage",
"testwatch": "mocha -w"
},
"bin": {
"swm": "./cli.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/tomru/switchmon.git"
},
"keywords": [
"xrandr",
"monitor"
],
"author": "Thomas Ruoff",
"license": "MIT",
"bugs": {
"url": "https://github.com/tomru/switchmon/issues"
},
"homepage": "https://github.com/tomru/switchmon#readme",
"dependencies": {
"minimist": "^1.2.0",
"xdg": "^0.1.1",
"xrandr-parse": "^0.1.1"
},
"devDependencies": {
"coveralls": "^2.11.8",
"istanbul": "^0.4.2",
"mocha": "^2.4.5",
"mocha-lcov-reporter": "^1.2.0",
"proxyquire": "^1.7.4",
"sinon": "^1.17.3"
}
}

37
swm
View File

@@ -1,37 +0,0 @@
#!/usr/bin/env bash
#
# Enable all connected montiors, disable all disconnected ones.
# Multiple monitors will be added to the right of previous ones.
#
# Author Thomas Ruoff <Thomas.Ruoff@gmail.com>
set -e
CONNECTED=$( xrandr | grep " connected" | awk '{print $1}')
DISCONNECTED=$( xrandr | grep "disconnected" | awk '{print $1}')
POSTCMD="herbstclient reload"
# turn off all disconnected monitors
XRANDR_OFF_OPTIONS=""
for mon in $DISCONNECTED; do
XRANDR_OFF_OPTIONS+=" --output $mon --off"
done
# turn on all connected monitors
XRANDR_ON_OPTIONS=""
LAST=""
for mon in $CONNECTED; do
XRANDR_ON_OPTIONS+=" --output $mon --auto"
if [ ! -z $LAST ]; then
XRANDR_ON_OPTIONS+=" --right-of $LAST"
fi
LAST=$mon
done
xrandr $XRANDR_ON_OPTIONS $XRANDR_OFF_OPTIONS &&\
echo Activated monitors: ${CONNECTED} &&\
$POSTCMD
exit $?

94
swm.js Normal file
View File

@@ -0,0 +1,94 @@
'use strict';
const xrandrParse = require('xrandr-parse');
const exec = require('child_process').exec;
function executeCmd(cmd, callback) {
exec(cmd, callback);
}
function getDevices(callback) {
executeCmd('xrandr', (err, stdout) => callback(err, err ? null : xrandrParse(stdout)));
}
function switchDevices(xrandrOptions, callback) {
executeCmd('xrandr ' + xrandrOptions, callback);
}
function executePostCmd(postCmd, callback) {
executeCmd(postCmd, callback);
}
function orderDeviceKeys(selectedDevices, devices) {
let orderedDeviceKeys = Object.keys(devices).sort();
// fix the sort order if monitors were explicitly selected
selectedDevices.reverse().forEach((monitor) => {
const index = orderedDeviceKeys.indexOf(monitor);
if (index < 0) {
console.error('Unkown monitor', monitor, '(ignored)');
return;
}
orderedDeviceKeys.splice(index, 1);
orderedDeviceKeys.unshift(monitor);
});
return orderedDeviceKeys;
}
function setActivationFlag(selectedDevices, devices) {
const result = {};
const selectByDefault = selectedDevices.length === 0;
Object.keys(devices).forEach(deviceKey => {
const device = Object.assign({}, devices[deviceKey]);
const isSelected = selectedDevices.indexOf(deviceKey) > -1;
if (isSelected || selectByDefault) {
if (device.connected) {
device.activate = true;
} else if (isSelected) {
console.error(deviceKey, 'not connected. Skipping...');
}
}
result[deviceKey] = device;
});
return result;
}
function generateXrandrOptions(selectedDevices, rawDevices) {
let xrandrOptions = '';
let prevDevice;
let devices = setActivationFlag(selectedDevices, rawDevices);
orderDeviceKeys(selectedDevices, devices).forEach(deviceKey => {
const device = devices[deviceKey];
const monitorOptions = ['', '--output', deviceKey];
if (!device.activate) {
monitorOptions.push('--off');
} else {
monitorOptions.push('--auto');
if (prevDevice) {
monitorOptions.push(['--right-of', prevDevice].join(' '));
}
prevDevice = deviceKey;
}
xrandrOptions += monitorOptions.join(' ');
});
// sanity check if at least one monitor is on
if (xrandrOptions.indexOf('--auto') === -1) {
throw new Error('Non of the given monitors are connected, aborting...');
}
return xrandrOptions;
}
module.exports.getDevices = getDevices;
module.exports.generateXrandrOptions = generateXrandrOptions;
module.exports.switchDevices = switchDevices;
module.exports.executePostCmd = executePostCmd;

184
test/cli.tests.js Normal file
View File

@@ -0,0 +1,184 @@
'use strict';
const sinon = require('sinon');
const assert = require('assert');
const proxyquire = require('proxyquire');
describe('cli', () => {
let sandbox;
let consoleLogSpy;
beforeEach(() => {
sandbox = sinon.sandbox.create();
consoleLogSpy = sandbox.spy(console, 'log');
});
afterEach(() => {
sandbox.restore();
});
it('shows help', () => {
const minimistStub = sandbox.stub();
minimistStub.returns({h: true});
const cli = proxyquire('../cli.js', {
'minimist': minimistStub,
'./usage.js': '[usage]'
});
assert.equal(consoleLogSpy.callCount, 1);
assert.equal(consoleLogSpy.args[0][0], '[usage]');
});
describe('device list', () => {
let minimistStub, getDevicesStub;
beforeEach(() => {
minimistStub = sandbox.stub();
minimistStub.returns({l: true});
getDevicesStub = sandbox.stub();
proxyquire('../cli.js', {
'minimist': minimistStub,
'./swm.js': {
getDevices: getDevicesStub
}
});
});
it('lists devices', () => {
getDevicesStub.args[0][0](null, {
LVDS1: {connected: true},
HDMI2: {connected: false}
});
assert.equal(getDevicesStub.callCount, 1);
assert.equal(consoleLogSpy.callCount, 3);
assert.equal(consoleLogSpy.args[0][0], 'Detected devices:\n');
assert.equal(consoleLogSpy.args[1].join(' '), 'LVDS1: Connected');
assert.equal(consoleLogSpy.args[2].join(' '), 'HDMI2: Disconnected');
});
it('throws error when listing devices fails', () => {
assert.throws(() => {
getDevicesStub.args[0][0]('[some err]');
}, /some err/);
});
});
describe('switching', () => {
let minimistStub;
let deviceData;
let getDevicesStub;
let generateXrandrOptionsStub;
let switchDevicesStub;
let executePostCmdStub;
beforeEach(() => {
minimistStub = sandbox.stub();
minimistStub.returns({
_: ['LVDS1'],
postCmd: '[some post cmd]'
});
deviceData = {
LVDS1: {connected: true},
HDMI2: {connected: false}
};
getDevicesStub = sandbox.stub();
generateXrandrOptionsStub = sandbox.stub().returns('[some xrandr options]');
switchDevicesStub = sandbox.stub();
executePostCmdStub = sandbox.stub();
const cli = proxyquire('../cli.js', {
'minimist': minimistStub,
'./swm.js': {
getDevices: getDevicesStub,
generateXrandrOptions: generateXrandrOptionsStub,
switchDevices: switchDevicesStub,
executePostCmd: executePostCmdStub
},
'./config.js': {}
});
});
it('calls getDevices', () => {
getDevicesStub.args[0][0](null, deviceData);
assert.equal(getDevicesStub.callCount, 1, 'calls device stub');
});
it('throws error when getDevices fails', () => {
assert.throws(() => {
getDevicesStub.args[0][0]('[some err]');
}, /some err/);
});
it('calls generateXrandrOptions', () => {
getDevicesStub.args[0][0](null, deviceData);
assert.equal(generateXrandrOptionsStub.callCount, 1, 'calls generateXrandrOptions');
assert.deepEqual(generateXrandrOptionsStub.args[0][0], ['LVDS1']);
});
it('calls switchDevices', () => {
getDevicesStub.args[0][0](null, deviceData);
assert.equal(switchDevicesStub.callCount, 1);
assert.equal(switchDevicesStub.args[0][0], '[some xrandr options]');
});
it('calls executePostCmd', () => {
getDevicesStub.args[0][0](null, deviceData);
assert.equal(executePostCmdStub.callCount, 1);
assert.equal(executePostCmdStub.args[0][0], '[some post cmd]');
});
});
describe('switching with profiles', () => {
let minimistStub;
let deviceData;
let getDevicesStub;
let generateXrandrOptionsStub;
let switchDevicesStub;
let executePostCmdStub;
beforeEach(() => {
minimistStub = sandbox.stub();
minimistStub.returns({
_: ['LVDS1'],
postCmd: '[some post cmd]',
profile: 'profile1'
});
deviceData = {
LVDS1: {connected: true},
HDMI2: {connected: false}
};
getDevicesStub = sandbox.stub();
generateXrandrOptionsStub = sandbox.stub().returns('[some xrandr options]');
switchDevicesStub = sandbox.stub();
executePostCmdStub = sandbox.stub();
proxyquire('../cli.js', {
'minimist': minimistStub,
'./swm.js': {
getDevices: getDevicesStub,
generateXrandrOptions: generateXrandrOptionsStub,
switchDevices: switchDevicesStub,
executePostCmd: executePostCmdStub
},
'./config.js': {
profiles: {
profile1: ['HDMI1', 'HDMI2']
}
}
});
});
it('calls generateXrandrOptions with profile settings', () => {
getDevicesStub.args[0][0](null, deviceData);
assert.equal(generateXrandrOptionsStub.callCount, 1, 'calls generateXrandrOptions');
assert.deepEqual(generateXrandrOptionsStub.args[0][0], ['HDMI1', 'HDMI2']);
});
});
});

57
test/config.tests.js Normal file
View File

@@ -0,0 +1,57 @@
'use strict';
const sinon = require('sinon');
const assert = require('assert');
const proxyquire = require('proxyquire').noCallThru();
describe('config', () => {
let sandbox;
let consoleLogSpy;
let xdgBasedirConfigStub;
beforeEach(() => {
sandbox = sinon.sandbox.create();
consoleLogSpy = sandbox.spy(console, 'log');
xdgBasedirConfigStub = sandbox.stub();
});
afterEach(() => {
sandbox.restore();
});
it('loads config if existent', () => {
xdgBasedirConfigStub.returns('./some_config.json');
const config = proxyquire('../config.js', {
'xdg': {
basedir: {
configPath: xdgBasedirConfigStub
}
},
'./some_config.json': {
postCmd: '[some postCmd]',
profiles: {
profile1: ['HDMI1', 'HDMI2'],
profile2: ['LVDS1']
}
}
});
assert.equal(config.postCmd, '[some postCmd]');
assert.deepEqual(config.profiles.profile1, ['HDMI1', 'HDMI2']);
assert.deepEqual(config.profiles.profile2, ['LVDS1']);
});
it('uses defaults if not existent', () => {
xdgBasedirConfigStub.returns('./not_existing_config.json');
const config = proxyquire('../config.js', {
'xdg': {
basedir: {
configPath: xdgBasedirConfigStub
}
}
});
assert.equal(config.postCmd, undefined);
assert.equal(Object.keys(config.profiles).length, 0);
});
});

182
test/swm.tests.js Normal file
View File

@@ -0,0 +1,182 @@
'use strict';
const sinon = require('sinon');
const assert = require('assert');
const proxyquire = require('proxyquire');
describe('swm', () => {
let sandbox;
beforeEach(() => {
sandbox = sinon.sandbox.create();
});
afterEach(() => {
sandbox.restore();
});
describe('getDevices', () => {
let execStub;
let xrandrParseStub;
let swm;
beforeEach(() => {
execStub = sandbox.stub();
xrandrParseStub = sandbox.stub();
swm = proxyquire('../swm.js', {
child_process: {
exec: execStub
},
'xrandr-parse': xrandrParseStub
});
});
it('calls exec', () => {
swm.getDevices(sandbox.stub());
assert.equal(execStub.callCount, 1);
assert.equal(execStub.args[0][0], 'xrandr');
});
it('passes error when exec passes error', (done) => {
const devices = swm.getDevices((err, devices) => {
assert.equal(xrandrParseStub.callCount, 0);
assert.equal(err, 'some error');
done();
});
const cb = execStub.args[0][1];
cb('some error');
});
it('parses result', (done) => {
xrandrParseStub.returns('[some result]');
swm.getDevices((err, devices) => {
assert.equal(xrandrParseStub.args[0][0], '[some stdout]');
assert.equal(xrandrParseStub.callCount, 1);
assert.equal(devices, '[some result]');
done();
});
const cb = execStub.args[0][1];
cb(null, '[some stdout]');
});
});
describe('generateXrandrOptions', () => {
let swm;
let devices;
beforeEach(() => {
devices = {
'HDMI1': {
connected: true
},
'HDMI2': {
connected: true,
},
'LVDS1': {
connected: false
},
'DP1': {
connected: true,
}
}
swm = require('../swm.js');
});
describe('activation of monitor', () => {
it('for connected monitors if non is selected', () => {
const result = swm.generateXrandrOptions([], devices);
assert(result.indexOf('--output HDMI1 --auto') > -1);
assert(result.indexOf('--output HDMI2 --auto') > -1);
assert(result.indexOf('--output LVDS1 --off') > -1);
});
it('for connected selected monitors', () => {
const result = swm.generateXrandrOptions(['HDMI2'], devices);
assert(result.indexOf('--output HDMI1 --off') > -1);
assert(result.indexOf('--output HDMI2 --auto') > -1);
assert(result.indexOf('--output LVDS1 --off') > -1);
});
it('skipps selected monitors that are not connected', () => {
const result = swm.generateXrandrOptions(['LVDS1', 'HDMI2'], devices);
assert(result.indexOf('--output HDMI2 --auto') > -1);
assert(result.indexOf('--output LVDS1 --off') > -1);
});
});
it('aligns monitor n to the right of monitor n-1', () => {
const result = swm.generateXrandrOptions(['HDMI1', 'HDMI2', 'DP1'], devices);
assert(result.indexOf('--output HDMI1 --auto') > -1);
assert(result.indexOf('--output HDMI2 --auto --right-of HDMI1') > -1);
assert(result.indexOf('--output DP1 --auto --right-of HDMI2') > -1);
});
it('throws when no selected monitor is connected', () => {
assert.throws(() => {swm.generateXrandrOptions(['LVDS1'], devices)});
assert.throws(() => {swm.generateXrandrOptions(['BOGUS'], devices)});
devices = {};
assert.throws(() => {swm.generateXrandrOptions([], devices)});
});
});
describe('switchDevices', () => {
let execStub;
let swm;
beforeEach(() => {
execStub = sandbox.stub();
swm = proxyquire('../swm.js', {
child_process: {
exec: execStub
}
});
});
it('calls exec with xrandr and opitons', () => {
swm.switchDevices('[some options]', sandbox.stub());
assert.equal(execStub.callCount, 1);
assert.equal(execStub.args[0][0], 'xrandr [some options]');
});
it('passes error when exec passes error', (done) => {
swm.switchDevices('[some options]', (err, devices) => {
assert.equal(err, 'some error');
done();
});
const cb = execStub.args[0][1];
cb('some error');
});
});
describe('executePostCmd', () => {
let execStub;
let swm;
beforeEach(() => {
execStub = sandbox.stub();
swm = proxyquire('../swm.js', {
child_process: {
exec: execStub
}
});
});
it('calls postCmd', () => {
swm.executePostCmd('[some cmd]');
assert.equal(execStub.callCount, 1);
assert.equal(execStub.args[0][0], '[some cmd]');
});
it('passes error when exec passes error', (done) => {
swm.executePostCmd('[some cmd]', (err, devices) => {
assert.equal(err, '[some error]');
done();
});
const cb = execStub.args[0][1];
cb('[some error]');
});
});
});

47
usage.js Normal file
View File

@@ -0,0 +1,47 @@
const usage = `Synopsis
swm
swm [monitor-1..montior-n]
swm --profile external
swm --list
Description
To turn on/off connected/disconnected monitors.
If no monitors are specified all connected monitors will be turned on and
placed from left to right in alphabetical order of their name.
If monitors monitor-1..monitor-n are specified these monitors will be turned
on and place them from left to right in the order given.
--profile profilename or -p profilename
If a profile is specified, the configured monitors will be turned on.
--postCmd "some cmd"
A post command is executed after switching the monitors. This is usefull to
tell your window manager to re-detect monitors, e.g. for herbstluftwm
herbstclient reload.
-l or swm --list
List all devices with the connectivity status.
Configuration
The configuration can be placed in $XDG_CONFIG_HOME/switchmon/config.json in
the form of
{
"postCmd": "some command",
"profiles": {
"internal": ["LVDS1"],
"external": ["HDMI1"],
"dual": ["LVDS1", "HDMI1"]
}
}
`;
module.exports = usage;