├── .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 |
--------------------------------------------------------------------------------