Skip to content

Commit

Permalink
Expose an API to get a list of screens
Browse files Browse the repository at this point in the history
Fixes #57
  • Loading branch information
sindresorhus committed Jul 2, 2018
1 parent f644404 commit 3ae0a45
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 4 deletions.
1 change: 1 addition & 0 deletions example.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const aperture = require('.');

async function main() {
const recorder = aperture();
console.log('Screens:', await aperture.screens());
console.log('Audio devices:', await aperture.audioDevices());
console.log('Preparing to record for 5 seconds');
await recorder.startRecording();
Expand Down
12 changes: 11 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,12 +146,22 @@ class Aperture {

module.exports = () => new Aperture();

module.exports.screens = async () => {
const stderr = await execa.stderr(BIN, ['list-screens']);

try {
return JSON.parse(stderr);
} catch (_) {
return stderr;
}
};

module.exports.audioDevices = async () => {
const stderr = await execa.stderr(BIN, ['list-audio-devices']);

try {
return JSON.parse(stderr);
} catch (err) {
} catch (_) {
return stderr;
}
};
Expand Down
13 changes: 13 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,19 @@ See [`example.js`](example.js) if you want to quickly try it out. *(The example

## API

### aperture.screens() -> `Promise<Array>`

Get a list of screens. The first screen is the primary screen.

Example:

```js
[{
id: 69732482,
name: 'Color LCD'
}]
```

### aperture.audioDevices() -> `Promise<Array>`

Get a list of audio devices.
Expand Down
11 changes: 10 additions & 1 deletion swift/aperture/DeviceList.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import Foundation
import AppKit
import AVFoundation
import CoreMediaIO

Expand All @@ -15,6 +15,15 @@ private func enableDalDevices() {
}

struct DeviceList {
static func screen() -> [[String: Any]] {
return NSScreen.screens.map {
[
"name": $0.name,
"id": $0.id
]
}
}

static func audio() -> [[String: String]] {
return AVCaptureDevice.devices(for: .audio).map {
[
Expand Down
6 changes: 6 additions & 0 deletions swift/aperture/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,17 @@ func usage() {
"""
Usage:
aperture <options>
aperture list-screens
aperture list-audio-devices
"""
)
}

if arguments.first == "list-screens" {
printErr(try toJson(DeviceList.screen()))
exit(0)
}

if arguments.first == "list-audio-devices" {
// Uses stderr because of unrelated stuff being outputted on stdout
printErr(try toJson(DeviceList.audio()))
Expand Down
55 changes: 53 additions & 2 deletions swift/aperture/util.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import Foundation
import AppKit
import AVFoundation

private final class StandardErrorOutputStream: TextOutputStream {
Expand All @@ -24,5 +24,56 @@ extension CMTime {
}

extension CGDirectDisplayID {
static let main: CGDirectDisplayID = CGMainDisplayID()
static let main = CGMainDisplayID()
}

extension NSScreen {
private func infoForCGDisplay(_ displayID: CGDirectDisplayID, options: Int) -> [AnyHashable: Any]? {
var iterator: io_iterator_t = 0

let result = IOServiceGetMatchingServices(kIOMasterPortDefault, IOServiceMatching("IODisplayConnect"), &iterator)
guard result == kIOReturnSuccess else {
print("Could not find services for IODisplayConnect: \(result)")
return nil
}

var service = IOIteratorNext(iterator)
while service != 0 {
let info = IODisplayCreateInfoDictionary(service, IOOptionBits(options)).takeRetainedValue() as! [AnyHashable: Any]

guard
let vendorID = info[kDisplayVendorID] as! UInt32?,
let productID = info[kDisplayProductID] as! UInt32?
else {
continue
}

if vendorID == CGDisplayVendorNumber(displayID) && productID == CGDisplayModelNumber(displayID) {
return info
}

service = IOIteratorNext(iterator)
}

return nil
}

var id: CGDirectDisplayID {
return deviceDescription[NSDeviceDescriptionKey("NSScreenNumber")] as! CGDirectDisplayID
}

var name: String {
guard let info = infoForCGDisplay(id, options: kIODisplayOnlyPreferredName) else {
return "Unknown screen"
}

guard
let localizedNames = info[kDisplayProductName] as? [String: Any],
let name = localizedNames.values.first as? String
else {
return "Unnamed screen"
}

return name
}
}

0 comments on commit 3ae0a45

Please sign in to comment.