mirror of
https://github.com/tomru/switchmon.git
synced 2026-03-03 06:27:23 +01:00
Compare commits
47 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
06855cee91 | ||
|
|
c71c1629e2 | ||
|
|
5d945c8f66 | ||
|
|
52ba27e092 | ||
|
|
272441dd70 | ||
|
|
fe99d75cb4 | ||
|
|
90ad436396 | ||
|
|
a64a14419b | ||
|
|
ef0b8de4e1 | ||
|
|
03f11a88d5 | ||
|
|
a82891c9e8 | ||
|
|
e881214463 | ||
|
|
f6f0de0e04 | ||
|
|
9d25ebfc33 | ||
|
|
adaa1ed68a | ||
|
|
c1c0f75c0a | ||
|
|
41e85ebb68 | ||
|
|
372eaab45e | ||
|
|
fa06e7914b | ||
|
|
412f26a9dd | ||
|
|
0adc89d2d9 | ||
|
|
c1e651245e | ||
|
|
77c1f3c674 | ||
|
|
15faee38c9 | ||
|
|
67695abfab | ||
|
|
128724bbc8 | ||
|
|
029b12332a | ||
|
|
067471dbc5 | ||
|
|
85cb7ba3f8 | ||
|
|
dc0552c10d | ||
|
|
196b9cb8c8 | ||
|
|
e3054967b4 | ||
|
|
3b0ee78cf4 | ||
|
|
a2a47cdf8d | ||
|
|
242a12d12a | ||
|
|
511b972c22 | ||
|
|
7d391334f1 | ||
|
|
31711c7191 | ||
|
|
0cff05484f | ||
|
|
d2e0e172f9 | ||
|
|
610fd07bec | ||
|
|
985d394872 | ||
|
|
04a8bf17eb | ||
|
|
d452fefc9c | ||
|
|
6dca960958 | ||
|
|
cbe0c518d8 | ||
|
|
919735bf5c |
14
.editorconfig
Normal file
14
.editorconfig
Normal 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
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
node_modules/
|
||||
switchmon-*.tgz
|
||||
coverage
|
||||
.lvimrc
|
||||
22
.jshintrc
Normal file
22
.jshintrc
Normal 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
2
.npmignore
Normal file
@@ -0,0 +1,2 @@
|
||||
.editorconfig
|
||||
.jshintrc
|
||||
4
.travis.yml
Normal file
4
.travis.yml
Normal file
@@ -0,0 +1,4 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- "5"
|
||||
install: npm install
|
||||
60
README.md
60
README.md
@@ -1,14 +1,58 @@
|
||||
[](https://travis-ci.org/tomru/switchmon) [](https://coveralls.io/github/tomru/switchmon?branch=master)
|
||||
|
||||
# SWM - Switch Monitors
|
||||
|
||||
Simple helper for turning on/off connected/disconnected monitors with `xrandr`.
|
||||
|
||||
It uses `xrandr` to detect on which connectors there are monitors connected. It
|
||||
then runs `xrandr` and sets all disconnected monitors to `off` and all connected
|
||||
monitors to `on`.
|
||||
## Synopsis
|
||||
`swm`
|
||||
|
||||
Monitors will be turned on with xrandr `--auto` option which tries to detect
|
||||
the optimal video setting.
|
||||
`swm [monitor-1..montior-n]`
|
||||
|
||||
If there are multiple monitors connected, each monitor is placed to the right
|
||||
of the previous monitor. This order is currently driven as xrandr lists the
|
||||
connectors.
|
||||
`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"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
Node.js > v4 on your PATH.
|
||||
|
||||
52
cli.js
Executable file
52
cli.js
Executable 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
21
config.js
Normal 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
40
package.json
Normal 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
37
swm
@@ -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
94
swm.js
Normal 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
184
test/cli.tests.js
Normal 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
57
test/config.tests.js
Normal 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
182
test/swm.tests.js
Normal 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
47
usage.js
Normal 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;
|
||||
Reference in New Issue
Block a user