├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── dependabot.yml └── workflows │ ├── ci.yml │ └── lint.yml ├── .gitignore ├── README.md ├── examples ├── example.js └── multiScreens.js ├── index.js ├── lib ├── darwin │ └── index.js ├── linux │ └── index.js ├── utils.js └── win32 │ ├── app.manifest │ ├── index.js │ └── screenCapture_1.3.2.bat ├── package-lock.json ├── package.json └── test.js /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Environment (please complete the following information):** 27 | - OS: [e.g. Window 10, Ubuntu 19.04] 28 | - Engine [e.g. Node, Electron] 29 | - Version [e.g. 12.0.3] 30 | 31 | **Additional context** 32 | Add any other context about the problem here. 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "npm" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | - package-ecosystem: "github-actions" # See documentation for possible values 13 | directory: "/" # Location of package manifests 14 | schedule: 15 | interval: "weekly" 16 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | jobs: 10 | build: 11 | strategy: 12 | fail-fast: false 13 | matrix: 14 | os: [ubuntu-latest, windows-latest] 15 | node-version: [16.x, 18.x, 20.x] 16 | 17 | runs-on: ${{ matrix.os }} 18 | 19 | steps: 20 | - uses: actions/checkout@v4 21 | - name: Install xrandr 22 | run: sudo apt-get update && sudo apt-get install -y x11-xserver-utils imagemagick 23 | if: runner.os == 'Linux' 24 | - name: Use Node.js ${{ matrix.node-version }} 25 | uses: actions/setup-node@v4 26 | with: 27 | node-version: ${{ matrix.node-version }} 28 | - run: npm ci 29 | - name: Run headless test 30 | uses: coactions/setup-xvfb@v1 31 | with: 32 | working-directory: ./ #optional 33 | run: npm test 34 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Style Check 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | jobs: 10 | build: 11 | strategy: 12 | fail-fast: false 13 | matrix: 14 | os: [ubuntu-latest] 15 | node-version: [18.x] 16 | 17 | runs-on: ${{ matrix.os }} 18 | 19 | steps: 20 | - uses: actions/checkout@v4 21 | - name: Use Node.js ${{ matrix.node-version }} 22 | uses: actions/setup-node@v4 23 | with: 24 | node-version: ${{ matrix.node-version }} 25 | - run: npm ci 26 | - name: Run headless test 27 | uses: GabrielBB/xvfb-action@v1 28 | with: 29 | working-directory: ./ #optional 30 | run: npm run lint 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | out.jpg 3 | *.exe 4 | *.log 5 | *.png 6 | .vscode 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # screenshot-desktop 2 | 3 | > Capture a screenshot of your local machine 4 | 5 | * Multi/Cross Platform 6 | * Linux: required ImageMagick `apt-get install imagemagick` 7 | * OSX: No dependencies required! 8 | * Windows: No dependencies required! 9 | * Promise based API 10 | * JPG output (by default) 11 | 12 | ## Install 13 | 14 | $ npm install --save screenshot-desktop 15 | 16 | ## Usage 17 | 18 | ```js 19 | const screenshot = require('screenshot-desktop') 20 | 21 | screenshot().then((img) => { 22 | // img: Buffer filled with jpg goodness 23 | // ... 24 | }).catch((err) => { 25 | // ... 26 | }) 27 | ``` 28 | 29 | ```js 30 | const screenshot = require('screenshot-desktop') 31 | 32 | screenshot({format: 'png'}).then((img) => { 33 | // img: Buffer filled with png goodness 34 | // ... 35 | }).catch((err) => { 36 | // ... 37 | }) 38 | ``` 39 | 40 | ```js 41 | screenshot.listDisplays().then((displays) => { 42 | // displays: [{ id, name }, { id, name }] 43 | screenshot({ screen: displays[displays.length - 1].id }) 44 | .then((img) => { 45 | // img: Buffer of screenshot of the last display 46 | }); 47 | }) 48 | ``` 49 | 50 | ```js 51 | screenshot.all().then((imgs) => { 52 | // imgs: an array of Buffers, one for each screen 53 | }) 54 | ``` 55 | 56 | ```js 57 | screenshot({ filename: 'shot.jpg' }).then((imgPath) => { 58 | // imgPath: absolute path to screenshot 59 | // created in current working directory named shot.png 60 | }); 61 | 62 | // absolute paths work too. so do pngs 63 | screenshot({ filename: '/Users/brian/Desktop/demo.png' }) 64 | ``` 65 | 66 | ## screenshot() options 67 | 68 | - `filename` Optional. Absolute or relative path to save output. 69 | - `format` Optional. Valid values `png|jpg`. 70 | - `linuxLibrary` Optional. Linux only. Valid values `scrot|imagemagick`. Which library to use. Note that scrot does not support format or screen selection. 71 | 72 | ## Licence 73 | 74 | MIT © [Ben Evans](https://bencevans.io) 75 | -------------------------------------------------------------------------------- /examples/example.js: -------------------------------------------------------------------------------- 1 | const screenshot = require('./') 2 | const fs = require('fs') 3 | 4 | screenshot().then((img) => { 5 | fs.writeFile('out.jpg', img, function (err) { 6 | if (err) { 7 | throw err 8 | } 9 | console.log('written to out.jpg') 10 | }) 11 | }).catch((err) => { 12 | throw err 13 | }) 14 | -------------------------------------------------------------------------------- /examples/multiScreens.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const screenshot = require('..') 3 | 4 | screenshot.listDisplays() 5 | .then((displays) => { 6 | console.log(displays) 7 | for (let index = 0; index < displays.length; index++) { 8 | const display = displays[index] 9 | const imgpath = path.join(__dirname, Date.now() + '_' + index + '.png') 10 | screenshot({ screen: display.id, filename: imgpath }).then((imgpath) => { 11 | console.log(imgpath) 12 | }).catch(err => { 13 | console.error(err) 14 | }) 15 | } 16 | }) 17 | 18 | screenshot.listDisplays() 19 | .then((displays) => { 20 | console.log(displays) 21 | for (let index = 0; index < displays.length; index++) { 22 | const display = displays[index] 23 | screenshot({ screen: display.id }).then((imgbuf) => { 24 | console.log(imgbuf) 25 | }).catch(err => { 26 | console.error(err) 27 | }) 28 | } 29 | }) 30 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'strict mode' 2 | 3 | if (process.platform === 'linux') { 4 | module.exports = require('./lib/linux') 5 | } else if (process.platform === 'darwin') { 6 | module.exports = require('./lib/darwin') 7 | } else if (process.platform === 'win32') { 8 | module.exports = require('./lib/win32') 9 | } else { 10 | module.exports = function unSupported () { 11 | return Promise.reject(new Error('Currently unsupported platform. Pull requests welcome!')) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lib/darwin/index.js: -------------------------------------------------------------------------------- 1 | const exec = require('child_process').exec 2 | const temp = require('temp') 3 | const fs = require('fs') 4 | const utils = require('../utils') 5 | const path = require('path') 6 | 7 | const { unlinkP, readAndUnlinkP } = utils 8 | 9 | function darwinSnapshot (options = {}) { 10 | const performScreenCapture = displays => new Promise((resolve, reject) => { 11 | // validate displayId 12 | const totalDisplays = displays.length 13 | if (totalDisplays === 0) { 14 | return reject(new Error('No displays detected try dropping screen option')) 15 | } 16 | const maxDisplayId = totalDisplays - 1 17 | const displayId = options.screen || 0 18 | if (!Number.isInteger(displayId) || displayId < 0 || displayId > maxDisplayId) { 19 | const validChoiceMsg = (maxDisplayId === 0) ? '(valid choice is 0 or drop screen option altogether)' : `(valid choice is an integer between 0 and ${maxDisplayId})` 20 | return reject(new Error(`Invalid choice of displayId: ${displayId} ${validChoiceMsg}`)) 21 | } 22 | 23 | const format = options.format || 'jpg' 24 | let filename 25 | let suffix 26 | if (options.filename) { 27 | const ix = options.filename.lastIndexOf('.') 28 | suffix = ix >= 0 ? options.filename.slice(ix) : `.${format}` 29 | filename = '"' + options.filename.replace(/"/g, '\\"') + '"' 30 | } else { 31 | suffix = `.${format}` 32 | } 33 | 34 | const tmpPaths = Array(displayId + 1) 35 | .fill(null) 36 | .map(() => temp.path({ suffix })) 37 | 38 | let pathsToUse = [] 39 | if (options.filename) { 40 | tmpPaths[displayId] = filename 41 | } 42 | pathsToUse = tmpPaths.slice(0, displayId + 1) 43 | 44 | exec('screencapture' + ' -x -t ' + format + ' ' + pathsToUse.join(' '), 45 | function (err, stdOut) { 46 | if (err) { 47 | return reject(err) 48 | } else if (options.filename) { 49 | resolve(path.resolve(options.filename)) 50 | } else { 51 | fs.readFile(tmpPaths[displayId], function (err, img) { 52 | if (err) { 53 | return reject(err) 54 | } 55 | Promise.all(pathsToUse.map(unlinkP)) 56 | .then(() => resolve(img)) 57 | .catch((err) => reject(err)) 58 | }) 59 | } 60 | }) 61 | }) 62 | 63 | return listDisplays().then((displays) => { return performScreenCapture(displays) }) 64 | } 65 | 66 | const EXAMPLE_DISPLAYS_OUTPUT = ` 67 | Graphics/Displays: 68 | 69 | Intel Iris: 70 | 71 | Chipset Model: Intel Iris 72 | Type: GPU 73 | Bus: Built-In 74 | VRAM (Dynamic, Max): 1536 MB 75 | Vendor: Intel (0x8086) 76 | Device ID: 0x0a2e 77 | Revision ID: 0x0009 78 | Displays: 79 | Color LCD: 80 | Display Type: Retina LCD 81 | Resolution: 2560 x 1600 Retina 82 | Retina: Yes 83 | Pixel Depth: 32-Bit Color (ARGB8888) 84 | Main Display: Yes 85 | Mirror: Off 86 | Online: Yes 87 | Built-In: Yes 88 | HP 22cwa: 89 | Resolution: 1920 x 1080 @ 60Hz (1080p) 90 | Pixel Depth: 32-Bit Color (ARGB8888) 91 | Display Serial Number: 6CM7201231 92 | Mirror: Off 93 | Online: Yes 94 | Rotation: Supported 95 | Television: Yes 96 | ` 97 | 98 | function extractEntries (output) { 99 | const entries = [] 100 | 101 | const entryPattern = /(\s*)(.*?):(.*)\n/g 102 | let match 103 | while ((match = entryPattern.exec(output)) !== null) { 104 | entries.push({ 105 | indent: match[1].length, 106 | key: match[2].trim(), 107 | value: match[3].trim() 108 | }) 109 | } 110 | 111 | return entries 112 | } 113 | 114 | function makeSubtree (currIndent, subtree, entries) { 115 | let entry 116 | while ((entry = entries.shift())) { 117 | if (entry.value === '') { 118 | if (currIndent < entry.indent) { 119 | while (entry.key in subtree) { 120 | entry.key += '_1' 121 | } 122 | subtree[entry.key] = {} 123 | makeSubtree(entry.indent, subtree[entry.key], entries) 124 | } else { 125 | entries.unshift(entry) 126 | return 127 | } 128 | } else { 129 | while (entry.key in subtree) { 130 | entry.key += '_1' 131 | } 132 | subtree[entry.key] = entry.value 133 | } 134 | } 135 | } 136 | 137 | function movePrimaryToHead (displays) { 138 | const primary = displays.filter(e => e.primary) 139 | const notPrimary = displays.filter(e => !e.primary) 140 | return [...primary, ...notPrimary] 141 | } 142 | 143 | function addId (displays) { 144 | let id = 0 145 | return displays 146 | .map(display => { 147 | return Object.assign({}, display, { id: id++ }) 148 | }) 149 | } 150 | 151 | function parseDisplaysOutput (output) { 152 | const tree = {} 153 | makeSubtree(-1, tree, extractEntries(output)) 154 | 155 | if (!tree['Graphics/Displays']) { 156 | return [] 157 | } 158 | 159 | const firstGpuKeys = Object.keys(tree['Graphics/Displays']) 160 | if (!firstGpuKeys || firstGpuKeys.length <= 0) { 161 | return [] 162 | } 163 | 164 | let displayinfos = [] 165 | 166 | firstGpuKeys.forEach(gpukey => { 167 | const gpu = tree['Graphics/Displays'][gpukey] 168 | if (gpu.Displays) { 169 | const temp = Object.entries(gpu.Displays) 170 | .map(([name, props]) => { 171 | const primary = props['Main Display'] === 'Yes' 172 | return { name, primary } 173 | }) 174 | displayinfos = displayinfos.concat(temp) 175 | } 176 | }) 177 | 178 | return addId(movePrimaryToHead(displayinfos)) 179 | } 180 | 181 | function listDisplays () { 182 | return new Promise((resolve, reject) => { 183 | exec( 184 | 'system_profiler SPDisplaysDataType', 185 | (err, stdout) => { 186 | if (err) { 187 | return reject(err) 188 | } 189 | resolve(parseDisplaysOutput(stdout)) 190 | }) 191 | }) 192 | } 193 | 194 | function all () { 195 | return new Promise((resolve, reject) => { 196 | listDisplays() 197 | .then((displays) => { 198 | const tmpPaths = displays.map(() => temp.path({ suffix: '.jpg' })) 199 | exec('screencapture -x -t jpg ' + tmpPaths.join(' '), function (err, stdOut) { 200 | if (err) { 201 | return reject(err) 202 | } else { 203 | Promise.all(tmpPaths.map(readAndUnlinkP)) 204 | .then(resolve) 205 | .catch(reject) 206 | } 207 | }) 208 | }) 209 | }) 210 | } 211 | 212 | darwinSnapshot.listDisplays = listDisplays 213 | darwinSnapshot.all = all 214 | darwinSnapshot.parseDisplaysOutput = parseDisplaysOutput 215 | darwinSnapshot.EXAMPLE_DISPLAYS_OUTPUT = EXAMPLE_DISPLAYS_OUTPUT 216 | 217 | module.exports = darwinSnapshot 218 | -------------------------------------------------------------------------------- /lib/linux/index.js: -------------------------------------------------------------------------------- 1 | const exec = require('child_process').exec 2 | const path = require('path') 3 | const defaultAll = require('../utils').defaultAll 4 | 5 | const EXAMPLE_DISPLAYS_OUTPUT = `Screen 0: minimum 320 x 200, current 5760 x 1080, maximum 8192 x 8192 6 | eDP-1 connected (normal left inverted right x axis y axis) 7 | 2560x1440 60.00 + 8 | 1920x1440 60.00 9 | 1856x1392 60.01 10 | 1792x1344 60.01 11 | 1920x1200 59.95 12 | 1920x1080 59.93 13 | 1600x1200 60.00 14 | 1680x1050 59.95 59.88 15 | 1600x1024 60.17 16 | 1400x1050 59.98 17 | 1280x1024 60.02 18 | 1440x900 59.89 19 | 1280x960 60.00 20 | 1360x768 59.80 59.96 21 | 1152x864 60.00 22 | 1024x768 60.04 60.00 23 | 960x720 60.00 24 | 928x696 60.05 25 | 896x672 60.01 26 | 960x600 60.00 27 | 960x540 59.99 28 | 800x600 60.00 60.32 56.25 29 | 840x525 60.01 59.88 30 | 800x512 60.17 31 | 700x525 59.98 32 | 640x512 60.02 33 | 720x450 59.89 34 | 640x480 60.00 59.94 35 | 680x384 59.80 59.96 36 | 576x432 60.06 37 | 512x384 60.00 38 | 400x300 60.32 56.34 39 | 320x240 60.05 40 | DP-1 disconnected (normal left inverted right x axis y axis) 41 | HDMI-1 connected primary 1920x1080+0+0 (normal left inverted right x axis y axis) 476mm x 268mm 42 | 1920x1080 60.00*+ 50.00 50.00 59.94 43 | 1680x1050 59.88 44 | 1600x900 60.00 45 | 1280x1024 60.02 46 | 1440x900 59.90 47 | 1280x800 59.91 48 | 1280x720 60.00 50.00 59.94 49 | 1024x768 60.00 50 | 800x600 60.32 51 | 720x576 50.00 52 | 720x480 60.00 59.94 53 | 640x480 60.00 59.94 54 | 720x400 70.08 55 | DP-2 disconnected (normal left inverted right x axis y axis) 56 | HDMI-2 disconnected (normal left inverted right x axis y axis) 57 | DP-2-1 connected 1920x1080+3840+0 (normal left inverted right x axis y axis) 476mm x 268mm 58 | 1920x1080 60.00*+ 50.00 50.00 59.94 59 | 1680x1050 59.88 60 | 1600x900 60.00 61 | 1280x1024 60.02 62 | 1440x900 59.90 63 | 1280x800 59.91 64 | 1280x720 60.00 50.00 59.94 65 | 1024x768 60.00 66 | 800x600 60.32 67 | 720x576 50.00 68 | 720x480 60.00 59.94 69 | 640x480 60.00 59.94 70 | 720x400 70.08 71 | DP-2-2 connected 1920x1080+1920+0 (normal left inverted right x axis y axis) 476mm x 268mm 72 | 1920x1080 60.00*+ 50.00 50.00 59.94 73 | 1680x1050 59.88 74 | 1600x900 60.00 75 | 1280x1024 60.02 76 | 1440x900 59.90 77 | 1280x800 59.91 78 | 1280x720 60.00 50.00 59.94 79 | 1024x768 60.00 80 | 800x600 60.32 81 | 720x576 50.00 82 | 720x480 60.00 59.94 83 | 640x480 60.00 59.94 84 | 720x400 70.08 85 | DP-2-3 disconnected (normal left inverted right x axis y axis)` 86 | 87 | function parseDisplaysOutput (out) { 88 | return out 89 | .split('\n') 90 | .filter(line => line.indexOf(' connected ') > 0) 91 | .filter(line => line.search(/\dx\d/) > 0) 92 | .map((line, ix) => { 93 | const parts = line.split(' ') 94 | const name = parts[0] 95 | const primary = parts[2] === 'primary' 96 | const crop = primary ? parts[3] : parts[2] 97 | const resParts = crop.split(/[x+]/) 98 | const width = +resParts[0] 99 | const height = +resParts[1] 100 | const offsetX = +resParts[2] 101 | const offsetY = +resParts[3] 102 | 103 | return { 104 | width, 105 | height, 106 | name, 107 | id: name, 108 | offsetX, 109 | offsetY, 110 | primary, 111 | crop 112 | } 113 | }) 114 | } 115 | 116 | function listDisplays () { 117 | return new Promise((resolve, reject) => { 118 | exec('xrandr --current', (err, stdout) => { 119 | if (err) { 120 | return reject(err) 121 | } 122 | return resolve(parseDisplaysOutput(stdout)) 123 | }) 124 | }) 125 | } 126 | 127 | function maxBuffer (screens) { 128 | let total = 0 129 | screens.forEach((screen) => { 130 | total += screen.height * screen.width 131 | }) 132 | return total 133 | } 134 | 135 | function guessFiletype (filename) { 136 | switch (path.extname(filename)) { 137 | case '.jpg': 138 | case '.jpeg': 139 | return 'jpeg' 140 | case '.png': 141 | return 'png' 142 | } 143 | 144 | return 'jpeg' 145 | } 146 | 147 | function linuxSnapshot (options = {}) { 148 | return new Promise((resolve, reject) => { 149 | listDisplays().then((screens) => { 150 | const screen = screens.find(options.screen ? screen => screen.id === options.screen : screen => screen.primary || screen.id === 'default') || screens[0] 151 | 152 | const filename = options.filename ? (options.filename.replace(/"/g, '\\"')) : '-' 153 | const execOptions = 154 | options.filename 155 | ? {} 156 | : { 157 | encoding: 'buffer', 158 | maxBuffer: maxBuffer(screens) 159 | } 160 | const filetype = options.format || guessFiletype(filename) 161 | 162 | let commandLine = '' 163 | switch (options.linuxLibrary) { 164 | case 'scrot': // Faster. Does not support crop. 165 | commandLine = `scrot "${filename}" -e -z "echo \\"${filename}\\""` 166 | break 167 | case 'imagemagick': 168 | default: 169 | commandLine = `import -silent -window root -crop ${screen.crop} -screen ${filetype}:"${filename}" ` 170 | break 171 | } 172 | 173 | exec( 174 | commandLine, 175 | execOptions, 176 | (err, stdout) => { 177 | if (err) { 178 | return reject(err) 179 | } else { 180 | return resolve(options.filename ? path.resolve(options.filename) : stdout) 181 | } 182 | }) 183 | }) 184 | }) 185 | } 186 | 187 | linuxSnapshot.listDisplays = listDisplays 188 | linuxSnapshot.parseDisplaysOutput = parseDisplaysOutput 189 | linuxSnapshot.EXAMPLE_DISPLAYS_OUTPUT = EXAMPLE_DISPLAYS_OUTPUT 190 | linuxSnapshot.all = () => defaultAll(linuxSnapshot) 191 | module.exports = linuxSnapshot 192 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | 3 | function unlinkP (path) { 4 | return new Promise((resolve, reject) => { 5 | fs.unlink(path, function (err) { 6 | if (err) { 7 | return reject(err) 8 | } 9 | return resolve() 10 | }) 11 | }) 12 | } 13 | 14 | function readFileP (path) { 15 | return new Promise((resolve, reject) => { 16 | fs.readFile(path, function (err, img) { 17 | if (err) { 18 | return reject(err) 19 | } 20 | resolve(img) 21 | }) 22 | }) 23 | } 24 | 25 | function readAndUnlinkP (path) { 26 | return new Promise((resolve, reject) => { 27 | readFileP(path) 28 | .then((img) => { 29 | unlinkP(path) 30 | .then(() => resolve(img)) 31 | .catch(reject) 32 | }) 33 | .catch(reject) 34 | }) 35 | } 36 | 37 | function defaultAll (snapshot) { 38 | return new Promise((resolve, reject) => { 39 | snapshot.listDisplays() 40 | .then((displays) => { 41 | const snapsP = displays 42 | .map(({ id }) => snapshot({ screen: id })) 43 | Promise.all(snapsP) 44 | .then(resolve) 45 | .catch(reject) 46 | }) 47 | .catch(reject) 48 | }) 49 | } 50 | 51 | module.exports = { 52 | unlinkP, 53 | readFileP, 54 | readAndUnlinkP, 55 | defaultAll 56 | } 57 | -------------------------------------------------------------------------------- /lib/win32/app.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | True/PM 5 | 6 | 7 | -------------------------------------------------------------------------------- /lib/win32/index.js: -------------------------------------------------------------------------------- 1 | const exec = require('child_process').exec 2 | const temp = require('temp') 3 | const path = require('path') 4 | const utils = require('../utils') 5 | const fs = require('fs') 6 | const os = require('os') 7 | 8 | const { 9 | readAndUnlinkP, 10 | defaultAll 11 | } = utils 12 | 13 | function copyToTemp () { 14 | const tmpBat = path.join(os.tmpdir(), 'screenCapture', 'screenCapture_1.3.2.bat') 15 | const tmpManifest = path.join(os.tmpdir(), 'screenCapture', 'app.manifest') 16 | const includeBat = path.join(__dirname, 'screenCapture_1.3.2.bat').replace('app.asar', 'app.asar.unpacked') 17 | const includeManifest = path.join(__dirname, 'app.manifest').replace('app.asar', 'app.asar.unpacked') 18 | if (!fs.existsSync(tmpBat)) { 19 | const tmpDir = path.join(os.tmpdir(), 'screenCapture') 20 | if (!fs.existsSync(tmpDir)) { 21 | fs.mkdirSync(tmpDir) 22 | } 23 | const sourceData = { 24 | bat: fs.readFileSync(includeBat), 25 | manifest: fs.readFileSync(includeManifest) 26 | } 27 | fs.writeFileSync(tmpBat, sourceData.bat) 28 | fs.writeFileSync(tmpManifest, sourceData.manifest) 29 | } 30 | return tmpBat 31 | } 32 | 33 | function windowsSnapshot (options = {}) { 34 | return new Promise((resolve, reject) => { 35 | const displayName = options.screen 36 | const format = options.format || 'jpg' 37 | const tmpPath = temp.path({ 38 | suffix: `.${format}` 39 | }) 40 | const imgPath = path.resolve(options.filename || tmpPath) 41 | 42 | const displayChoice = displayName ? ` /d "${displayName}"` : '' 43 | 44 | const tmpBat = copyToTemp() 45 | 46 | exec('"' + tmpBat + '" "' + imgPath + '" ' + displayChoice, { 47 | cwd: path.join(os.tmpdir(), 'screenCapture'), 48 | windowsHide: true 49 | }, (err, stdout) => { 50 | if (err) { 51 | return reject(err) 52 | } else { 53 | if (options.filename) { 54 | resolve(imgPath) 55 | } else { 56 | readAndUnlinkP(tmpPath) 57 | .then(resolve) 58 | .catch(reject) 59 | } 60 | } 61 | }) 62 | }) 63 | } 64 | 65 | const EXAMPLE_DISPLAYS_OUTPUT = '\r\nC:\\Users\\devetry\\screenshot-desktop\\lib\\win32>// 2>nul || \r\n\\.\\DISPLAY1;0;1920;1080;0\r\n\\.\\DISPLAY2;0;3840;1080;1920\r\n' 66 | 67 | function parseDisplaysOutput (output) { 68 | const displaysStartPattern = /2>nul {2}\|\| / 69 | const { 70 | 0: match, 71 | index 72 | } = displaysStartPattern.exec(output) 73 | return output.slice(index + match.length) 74 | .split('\n') 75 | .map(s => s.replace(/[\n\r]/g, '')) 76 | .map(s => s.match(/(.*?);(.?\d+);(.?\d+);(.?\d+);(.?\d+);(.?\d*[\.,]?\d+)/)) // eslint-disable-line 77 | .filter(s => s) 78 | .map(m => ({ 79 | id: m[1], 80 | name: m[1], 81 | top: +m[2], 82 | right: +m[3], 83 | bottom: +m[4], 84 | left: +m[5], 85 | dpiScale: +m[6].replace(',', '.') 86 | })) 87 | .map(d => Object.assign(d, { 88 | height: d.bottom - d.top, 89 | width: d.right - d.left 90 | })) 91 | } 92 | 93 | function listDisplays () { 94 | return new Promise((resolve, reject) => { 95 | const tmpBat = copyToTemp() 96 | exec( 97 | '"' + tmpBat + '" /list', { 98 | cwd: path.join(os.tmpdir(), 'screenCapture') 99 | }, 100 | (err, stdout) => { 101 | if (err) { 102 | return reject(err) 103 | } 104 | resolve(parseDisplaysOutput(stdout)) 105 | }) 106 | }) 107 | } 108 | 109 | windowsSnapshot.listDisplays = listDisplays 110 | windowsSnapshot.availableDisplays = listDisplays 111 | windowsSnapshot.parseDisplaysOutput = parseDisplaysOutput 112 | windowsSnapshot.EXAMPLE_DISPLAYS_OUTPUT = EXAMPLE_DISPLAYS_OUTPUT 113 | windowsSnapshot.all = () => defaultAll(windowsSnapshot) 114 | 115 | module.exports = windowsSnapshot 116 | -------------------------------------------------------------------------------- /lib/win32/screenCapture_1.3.2.bat: -------------------------------------------------------------------------------- 1 | // 2>nul||@goto :batch 2 | /* 3 | :batch 4 | @echo off 5 | setlocal enableDelayedExpansion 6 | 7 | :: find csc.exe 8 | set "csc=" 9 | for /r "%SystemRoot%\Microsoft.NET\Framework\" %%# in ("*csc.exe") do set "csc=%%#" 10 | 11 | if not exist "%csc%" ( 12 | echo no .net framework installed 13 | exit /b 10 14 | ) 15 | 16 | if not exist "%~n0.exe" ( 17 | call %csc% /nologo /r:"Microsoft.VisualBasic.dll" /win32manifest:"app.manifest" /out:"%~n0.exe" "%~dpsfnx0" || ( 18 | exit /b !errorlevel! 19 | ) 20 | ) 21 | %~n0.exe %* 22 | endlocal & exit /b %errorlevel% 23 | 24 | */ 25 | 26 | // reference 27 | // https://gallery.technet.microsoft.com/scriptcenter/eeff544a-f690-4f6b-a586-11eea6fc5eb8 28 | 29 | using System; 30 | using System.Runtime.InteropServices; 31 | using System.Drawing; 32 | using System.Drawing.Imaging; 33 | using System.Collections.Generic; 34 | using Microsoft.VisualBasic; 35 | 36 | 37 | 38 | /// Provides functions to capture the entire screen, or a particular window, and save it to a file. 39 | 40 | public class ScreenCapture 41 | { 42 | 43 | static String deviceName = ""; 44 | static Image capturedImage = null; 45 | 46 | /// Creates an Image object containing a screen shot the active window 47 | 48 | public Image CaptureActiveWindow() 49 | { 50 | return CaptureWindow(User32.GetForegroundWindow()); 51 | } 52 | 53 | /// Creates an Image object containing a screen shot of the entire desktop 54 | 55 | public Image CaptureScreen() 56 | { 57 | if (!deviceName.Equals("")) 58 | { 59 | CaptureSpecificWindow(); 60 | if (capturedImage != null) 61 | { 62 | return capturedImage; 63 | } 64 | Console.WriteLine("Unable to capture image... using main display"); 65 | } 66 | return CaptureWindow(User32.GetDesktopWindow()); 67 | } 68 | 69 | /// Creates an Image object containing a screen shot of a specific window 70 | 71 | private Image CaptureWindow(IntPtr handle) 72 | { 73 | // get te hDC of the target window 74 | IntPtr hdcSrc = User32.GetWindowDC(handle); 75 | // get the size 76 | User32.RECT windowRect = new User32.RECT(); 77 | User32.GetWindowRect(handle, ref windowRect); 78 | 79 | Image img = CaptureWindowFromDC(handle, hdcSrc, windowRect); 80 | User32.ReleaseDC(handle, hdcSrc); 81 | return img; 82 | } 83 | private static Image CaptureWindowFromDC(IntPtr handle, IntPtr hdcSrc, User32.RECT windowRect){ 84 | // get the size 85 | int width = windowRect.right - windowRect.left; 86 | int height = windowRect.bottom - windowRect.top; 87 | // create a device context we can copy to 88 | IntPtr hdcDest = GDI32.CreateCompatibleDC(hdcSrc); 89 | // create a bitmap we can copy it to, 90 | // using GetDeviceCaps to get the width/height 91 | IntPtr hBitmap = GDI32.CreateCompatibleBitmap(hdcSrc, width, height); 92 | // select the bitmap object 93 | IntPtr hOld = GDI32.SelectObject(hdcDest, hBitmap); 94 | // bitblt over 95 | GDI32.BitBlt(hdcDest, 0, 0, width, height, hdcSrc, windowRect.left, windowRect.top, GDI32.SRCCOPY); 96 | // restore selection 97 | GDI32.SelectObject(hdcDest, hOld); 98 | // clean up 99 | GDI32.DeleteDC(hdcDest); 100 | // get a .NET image object for it 101 | Image img = Image.FromHbitmap(hBitmap); 102 | // free up the Bitmap object 103 | GDI32.DeleteObject(hBitmap); 104 | return img; 105 | } 106 | 107 | public void CaptureActiveWindowToFile(string filename, ImageFormat format) 108 | { 109 | Image img = CaptureActiveWindow(); 110 | img.Save(filename, format); 111 | } 112 | 113 | public void CaptureScreenToFile(string filename, ImageFormat format) 114 | { 115 | Image img = CaptureScreen(); 116 | img.Save(filename, format); 117 | } 118 | 119 | static bool fullscreen = true; 120 | static String file = "screenshot.bmp"; 121 | static System.Drawing.Imaging.ImageFormat format = System.Drawing.Imaging.ImageFormat.Bmp; 122 | static String windowTitle = ""; 123 | static List _monitorInfos; 124 | 125 | static void parseArguments() 126 | { 127 | String[] arguments = Environment.GetCommandLineArgs(); 128 | if (arguments.Length == 1) 129 | { 130 | printHelp(); 131 | Environment.Exit(0); 132 | } 133 | if (arguments[1].ToLower().Equals("/h") || arguments[1].ToLower().Equals("/help")) 134 | { 135 | printHelp(); 136 | Environment.Exit(0); 137 | } 138 | if (arguments[1].ToLower().Equals("/l") || arguments[1].ToLower().Equals("/list")) 139 | { 140 | PrintMonitorInfo(); 141 | Environment.Exit(0); 142 | } 143 | 144 | file = arguments[1]; 145 | Dictionary formats = 146 | new Dictionary(); 147 | 148 | formats.Add("bmp", System.Drawing.Imaging.ImageFormat.Bmp); 149 | formats.Add("emf", System.Drawing.Imaging.ImageFormat.Emf); 150 | formats.Add("exif", System.Drawing.Imaging.ImageFormat.Exif); 151 | formats.Add("jpg", System.Drawing.Imaging.ImageFormat.Jpeg); 152 | formats.Add("jpeg", System.Drawing.Imaging.ImageFormat.Jpeg); 153 | formats.Add("gif", System.Drawing.Imaging.ImageFormat.Gif); 154 | formats.Add("png", System.Drawing.Imaging.ImageFormat.Png); 155 | formats.Add("tiff", System.Drawing.Imaging.ImageFormat.Tiff); 156 | formats.Add("wmf", System.Drawing.Imaging.ImageFormat.Wmf); 157 | 158 | 159 | String ext = ""; 160 | if (file.LastIndexOf('.') > -1) 161 | { 162 | ext = file.ToLower().Substring(file.LastIndexOf('.') + 1, file.Length - file.LastIndexOf('.') - 1); 163 | } 164 | else 165 | { 166 | Console.WriteLine("Invalid file name - no extension"); 167 | Environment.Exit(7); 168 | } 169 | 170 | try 171 | { 172 | format = formats[ext]; 173 | } 174 | catch (Exception e) 175 | { 176 | Console.WriteLine("Probably wrong file format:" + ext); 177 | Console.WriteLine(e.ToString()); 178 | Environment.Exit(8); 179 | } 180 | 181 | if (arguments.Length <= 2){ 182 | return; 183 | } 184 | 185 | if (arguments[2].ToLower().Equals("/d") || arguments[2].ToLower().Equals("/display")){ 186 | if (arguments.Length == 2) { 187 | Console.WriteLine("Must specify a display if passing /display"); 188 | Environment.Exit(9); 189 | } 190 | deviceName = arguments[3]; 191 | } 192 | else if (arguments.Length > 2) 193 | { 194 | windowTitle = arguments[2]; 195 | fullscreen = false; 196 | } 197 | 198 | } 199 | 200 | static void printHelp() 201 | { 202 | //clears the extension from the script name 203 | String scriptName = Environment.GetCommandLineArgs()[0]; 204 | scriptName = scriptName.Substring(0, scriptName.Length); 205 | Console.WriteLine(scriptName + " captures the screen or the active window and saves it to a file."); 206 | Console.WriteLine(""); 207 | Console.WriteLine("Usage:"); 208 | Console.WriteLine(" " + scriptName + " filename [WindowTitle]"); 209 | Console.WriteLine(""); 210 | Console.WriteLine("filename - the file where the screen capture will be saved"); 211 | Console.WriteLine(" allowed file extensions are - Bmp,Emf,Exif,Gif,Icon,Jpeg,Png,Tiff,Wmf."); 212 | Console.WriteLine("WindowTitle - instead of capture whole screen you can point to a window "); 213 | Console.WriteLine(" with a title which will put on focus and captuted."); 214 | Console.WriteLine(" For WindowTitle you can pass only the first few characters."); 215 | Console.WriteLine(" If don't want to change the current active window pass only \"\""); 216 | Console.WriteLine(""); 217 | Console.WriteLine(" " + scriptName + " (/l | /list)"); 218 | Console.WriteLine(""); 219 | Console.WriteLine("List the available displays"); 220 | Console.WriteLine(""); 221 | Console.WriteLine(" " + scriptName + " filename (/d | /display) displayName"); 222 | Console.WriteLine(""); 223 | Console.WriteLine("filename - as above"); 224 | Console.WriteLine("displayName - a display name optained from running the script with /list"); 225 | } 226 | 227 | public static void Main() 228 | { 229 | parseArguments(); 230 | ScreenCapture sc = new ScreenCapture(); 231 | if (!fullscreen && !windowTitle.Equals("")) 232 | { 233 | try 234 | { 235 | 236 | Interaction.AppActivate(windowTitle); 237 | Console.WriteLine("setting " + windowTitle + " on focus"); 238 | } 239 | catch (Exception e) 240 | { 241 | Console.WriteLine("Probably there's no window like " + windowTitle); 242 | Console.WriteLine(e.ToString()); 243 | Environment.Exit(9); 244 | } 245 | 246 | 247 | } 248 | try 249 | { 250 | if (fullscreen) 251 | { 252 | Console.WriteLine("Taking a capture of the whole screen to " + file); 253 | sc.CaptureScreenToFile(file, format); 254 | } 255 | else 256 | { 257 | Console.WriteLine("Taking a capture of the active window to " + file); 258 | sc.CaptureActiveWindowToFile(file, format); 259 | } 260 | } 261 | catch (Exception e) 262 | { 263 | Console.WriteLine("Check if file path is valid " + file); 264 | Console.WriteLine(e.ToString()); 265 | } 266 | } 267 | 268 | /// Helper class containing Gdi32 API functions 269 | 270 | private class GDI32 271 | { 272 | 273 | public const int SRCCOPY = 0x00CC0020; // BitBlt dwRop parameter 274 | [DllImport("gdi32.dll")] 275 | public static extern bool BitBlt(IntPtr hObject, int nXDest, int nYDest, 276 | int nWidth, int nHeight, IntPtr hObjectSource, 277 | int nXSrc, int nYSrc, int dwRop); 278 | [DllImport("gdi32.dll")] 279 | public static extern IntPtr CreateCompatibleBitmap(IntPtr hDC, int nWidth, 280 | int nHeight); 281 | [DllImport("gdi32.dll")] 282 | public static extern IntPtr CreateCompatibleDC(IntPtr hDC); 283 | [DllImport("gdi32.dll")] 284 | public static extern bool DeleteDC(IntPtr hDC); 285 | [DllImport("gdi32.dll")] 286 | public static extern bool DeleteObject(IntPtr hObject); 287 | [DllImport("gdi32.dll")] 288 | public static extern IntPtr SelectObject(IntPtr hDC, IntPtr hObject); 289 | } 290 | 291 | 292 | /// Helper class containing User32 API functions 293 | 294 | public class User32 295 | { 296 | [StructLayout(LayoutKind.Sequential)] 297 | public struct RECT 298 | { 299 | public int left; 300 | public int top; 301 | public int right; 302 | public int bottom; 303 | } 304 | 305 | [DllImport("user32.dll")] 306 | public static extern IntPtr GetDC(IntPtr hWnd); 307 | [DllImport("user32.dll")] 308 | public static extern IntPtr GetDesktopWindow(); 309 | [DllImport("user32.dll")] 310 | public static extern IntPtr GetWindowDC(IntPtr hWnd); 311 | [DllImport("user32.dll")] 312 | public static extern IntPtr ReleaseDC(IntPtr hWnd, IntPtr hDC); 313 | [DllImport("user32.dll")] 314 | public static extern IntPtr GetWindowRect(IntPtr hWnd, ref RECT rect); 315 | [DllImport("user32.dll")] 316 | public static extern IntPtr GetForegroundWindow(); 317 | 318 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] 319 | public struct MONITORINFOEX 320 | { 321 | public uint size; 322 | public RECT Monitor; 323 | public RECT WorkArea; 324 | public uint Flags; 325 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] 326 | public string DeviceName; 327 | } 328 | 329 | [DllImport("user32.dll", CharSet = CharSet.Unicode)] 330 | public static extern bool GetMonitorInfo(IntPtr hMonitor, ref MONITORINFOEX lpmi); 331 | 332 | public delegate bool MonitorEnumDelegate(IntPtr hMonitor, IntPtr hdcMonitor, ref RECT lprcMonitor, IntPtr dwData); 333 | 334 | [DllImport("user32.dll")] 335 | public static extern bool EnumDisplayMonitors(IntPtr hdc, IntPtr lprcClip, MonitorEnumDelegate lpfnEnum, IntPtr dwData); 336 | } 337 | 338 | public class Shcore 339 | { 340 | [DllImport("Shcore.dll")] 341 | public static extern IntPtr GetDpiForMonitor(IntPtr hMonitor, int dpiType, out uint dpiX, out uint dpiY); 342 | } 343 | 344 | private class MonitorInfoWithHandle 345 | { 346 | public IntPtr MonitorHandle { get; private set; } 347 | public User32.MONITORINFOEX MonitorInfo { get; private set; } 348 | public float DpiScale { get; private set; } 349 | public MonitorInfoWithHandle(IntPtr monitorHandle, User32.MONITORINFOEX monitorInfo, float dpiScale) 350 | { 351 | MonitorHandle = monitorHandle; 352 | MonitorInfo = monitorInfo; 353 | DpiScale = dpiScale; 354 | } 355 | } 356 | private static bool MonitorEnum(IntPtr hMonitor, IntPtr hdcMonitor, ref User32.RECT lprcMonitor, IntPtr dwData) 357 | { 358 | var mi = new User32.MONITORINFOEX(); 359 | mi.size = (uint)Marshal.SizeOf(mi); 360 | User32.GetMonitorInfo(hMonitor, ref mi); 361 | uint dpiX, dpiY; 362 | Shcore.GetDpiForMonitor(hMonitor, 0, out dpiX, out dpiY); 363 | float dpiScale = ((float) dpiX) / 96; 364 | 365 | _monitorInfos.Add(new MonitorInfoWithHandle(hMonitor, mi, dpiScale)); 366 | return true; 367 | } 368 | private static bool CaptureMonitorEnum(IntPtr hMonitor, IntPtr hdcMonitor, ref User32.RECT lprcMonitor, IntPtr dwData) 369 | { 370 | var mi = new User32.MONITORINFOEX(); 371 | mi.size = (uint)Marshal.SizeOf(mi); 372 | User32.GetMonitorInfo(hMonitor, ref mi); 373 | if (mi.DeviceName.ToLower().Equals(deviceName.ToLower())) { 374 | Console.WriteLine("hMonitor is {0}, hdcMonitor is {1}", hMonitor, hdcMonitor); 375 | capturedImage = CaptureWindowFromDC(hMonitor, hdcMonitor, lprcMonitor); 376 | } 377 | return true; 378 | } 379 | public static void CaptureSpecificWindow() 380 | { 381 | IntPtr hdc = User32.GetDC(IntPtr.Zero); 382 | User32.EnumDisplayMonitors(hdc, IntPtr.Zero, CaptureMonitorEnum, IntPtr.Zero); 383 | User32.ReleaseDC(IntPtr.Zero, hdc); 384 | } 385 | private static List GetMonitors() 386 | { 387 | _monitorInfos = new List(); 388 | 389 | User32.EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, MonitorEnum, IntPtr.Zero); 390 | 391 | return _monitorInfos; 392 | } 393 | 394 | public static void PrintMonitorInfo() 395 | { 396 | var mis = GetMonitors(); 397 | foreach (var mi in mis) 398 | { 399 | Console.WriteLine("{0};{1};{2};{3};{4};{5}", 400 | mi.MonitorInfo.DeviceName, 401 | mi.MonitorInfo.Monitor.top, 402 | mi.MonitorInfo.Monitor.right, 403 | mi.MonitorInfo.Monitor.bottom, 404 | mi.MonitorInfo.Monitor.left, 405 | mi.DpiScale); 406 | } 407 | } 408 | } 409 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "screenshot-desktop", 3 | "version": "1.15.1", 4 | "description": "Capture a screenshot of your local machine", 5 | "main": "index.js", 6 | "dependencies": { 7 | "temp": "^0.9.4" 8 | }, 9 | "devDependencies": { 10 | "ava": "^6.1.1", 11 | "standard": "^17.0.0" 12 | }, 13 | "ava": { 14 | "timeout": "1m" 15 | }, 16 | "scripts": { 17 | "test": "ava", 18 | "semantic-release": "semantic-release", 19 | "lint": "standard" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "https://github.com/bencevans/screenshot-desktop.git" 24 | }, 25 | "keywords": [ 26 | "screenshot", 27 | "screengrab", 28 | "screen", 29 | "desktop", 30 | "laptop", 31 | "x11" 32 | ], 33 | "author": "Ben Evans (https://bencevans.io)", 34 | "license": "MIT", 35 | "bugs": { 36 | "url": "https://github.com/bencevans/screenshot-desktop/issues" 37 | }, 38 | "homepage": "https://github.com/bencevans/screenshot-desktop#readme", 39 | "funding": [ 40 | { 41 | "type": "github", 42 | "url": "https://github.com/sponsors/bencevans" 43 | } 44 | ], 45 | "release": { 46 | "branches": [ 47 | "main" 48 | ] 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | const test = require('ava') 2 | const { path: tempPathSync } = require('temp') 3 | const { existsSync, unlinkSync } = require('fs') 4 | const screenshot = require('./') 5 | 6 | test.before(async () => { 7 | return screenshot.listDisplays().then(displays => { 8 | console.log('Displays:', JSON.stringify(displays, null, 2), '\n') 9 | }) 10 | }) 11 | 12 | test('screenshot', t => { 13 | t.plan(1) 14 | return screenshot().then(img => { 15 | t.truthy(Buffer.isBuffer(img)) 16 | }) 17 | }) 18 | 19 | function checkDisplays (t, displays) { 20 | t.truthy(Array.isArray(displays)) 21 | displays.forEach(disp => { 22 | t.truthy(disp.name) 23 | t.truthy(disp.id !== undefined) 24 | }) 25 | } 26 | 27 | test('screenshot each display', t => { 28 | if (screenshot.availableDisplays) { 29 | return screenshot.availableDisplays().then(displays => { 30 | checkDisplays(t, displays) 31 | 32 | displays.forEach(display => { 33 | screenshot(display.id) 34 | }) 35 | }) 36 | } else { 37 | t.pass() 38 | } 39 | }) 40 | 41 | test('screenshot to a file', t => { 42 | t.plan(1) 43 | const tmpName = tempPathSync({ suffix: '.jpg' }) 44 | return screenshot({ filename: tmpName }).then(() => { 45 | t.truthy(existsSync(tmpName)) 46 | unlinkSync(tmpName) 47 | }) 48 | }) 49 | 50 | test('screenshot specific screen to a file', t => { 51 | t.plan(1) 52 | const tmpName = tempPathSync({ suffix: '.jpg' }) 53 | return screenshot({ filename: tmpName, screen: 0 }).then(() => { 54 | t.truthy(existsSync(tmpName)) 55 | unlinkSync(tmpName) 56 | }) 57 | }) 58 | 59 | test('screenshot to a file with a space', t => { 60 | // https://github.com/bencevans/screenshot-desktop/issues/12 61 | t.plan(1) 62 | const tmpName = tempPathSync({ suffix: '.jpg' }) 63 | return screenshot({ filename: tmpName }).then(() => { 64 | t.truthy(existsSync(tmpName)) 65 | unlinkSync(tmpName) 66 | }) 67 | }) 68 | 69 | test('parse display output', t => { 70 | if (screenshot.EXAMPLE_DISPLAYS_OUTPUT && screenshot.parseDisplaysOutput) { 71 | const disps = screenshot.parseDisplaysOutput(screenshot.EXAMPLE_DISPLAYS_OUTPUT) 72 | checkDisplays(t, disps) 73 | } 74 | }) 75 | --------------------------------------------------------------------------------