Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GPII-1903: Created initial framework for loading macOS-specific components #2

Open
wants to merge 34 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
56b4566
GPII-1903: Created initial framework for loading macOS-specific compo…
christopher-rtf Dec 13, 2019
fcbac5e
GPII-1903: Pulled in official BSD license text; updated copyright year
christopher-rtf Dec 13, 2019
81528be
GPII-1903: Added core Swift N-API plug-in architecture
christopher-rtf Feb 4, 2020
ff544d9
GPII-1903: Added build script during postinstall, updated build paths
christopher-rtf Feb 4, 2020
73b55e7
GPII-1903: Updated universal package dependency
christopher-rtf Feb 5, 2020
91321f0
GPII-1903: Updated gpii-universal dependency
christopher-rtf Feb 10, 2020
e04bad0
GPII-1903: Exported gpii.macos.native and simplified NAPIValueType co…
christopher-rtf Feb 13, 2020
f464088
GPII-1903: Added Swift N-API support for Arrays; added .unsupported type
christopher-rtf Feb 13, 2020
f451e22
GPII-1903: Added USB enumeration, safe drive ejection, and 'open dire…
christopher-rtf Feb 14, 2020
0c9f936
GPII-1903: Updated project files (GUIDs) to fix compiler glitch
christopher-rtf Feb 14, 2020
333ba9a
GPII-1903: Added Swift N-API support for Booleans
christopher-rtf Feb 18, 2020
75ac809
GPII-1903: Added get/set functions for audio volume and mute state
christopher-rtf Feb 18, 2020
ec44c37
GPII-1903: Updated gpii-universal reference to add Volume settings ha…
christopher-rtf Feb 18, 2020
67ed5ef
GPII-1903: Updated gpii-universal reference
christopher-rtf Feb 18, 2020
061c5c8
GPII-1903: Updated gpii-universal reference
christopher-rtf Feb 18, 2020
f72e65b
GPII-1903: Updated volume logic for closest symmetry to Windows imple…
christopher-rtf Feb 18, 2020
13980cd
GPII-1903: Added native functions for restarting system processes (Do…
christopher-rtf Feb 19, 2020
a0569ac
GPII-1903: Added native functions to get/set system languages
christopher-rtf Feb 19, 2020
24c8557
GPII-1903: Added native functions to translate language codes to huma…
christopher-rtf Feb 19, 2020
a457752
GPII-1903: Updated MorphicLanguage for enhanced compatibility with Sy…
christopher-rtf Feb 23, 2020
6d8e32c
GPII-1903: Updated gpii-universal commit tag (to add Language setting…
christopher-rtf Feb 23, 2020
b04bdaf
GPII-1903: Updated gpii-universal commit tag
christopher-rtf Feb 23, 2020
99b6685
GPII-1903: Added 'clean' pre-step to native build process
christopher-rtf Feb 25, 2020
f5bd004
GPII-1903: Updated gpii-universal commit tag
christopher-rtf Feb 25, 2020
3343aa3
GPII-1903: Added support for Optional (nil) argument and return value…
christopher-rtf Feb 25, 2020
952c8fd
GPII-1903: Added preliminary native code for set/get of display resol…
christopher-rtf Feb 25, 2020
44d1574
GPII-1903: Added support for briding JavaScript Objects with Swift St…
christopher-rtf Mar 3, 2020
453e19d
GPII-1903: Added displaySettingsHandler and dpiOffset function
christopher-rtf Mar 3, 2020
26d7054
GPII-1903: Updatetd gpii-universal commit tag to add display settings…
christopher-rtf Mar 3, 2020
cb46999
GPII-1903: Updated gpii-universal commit tag to add display settings …
christopher-rtf Mar 3, 2020
0744dfa
Merge branch 'GPII-1903' of https://github.com/christopher-rtf/gpii-m…
christopher-rtf Mar 3, 2020
52d1ff6
GPII-1903: Added native functions for finding the topmost window and …
christopher-rtf Mar 6, 2020
6e1ed46
GPII-1903: Added support for JavaScript Throwables/Errors and Swift->…
christopher-rtf Mar 8, 2020
85ec4c1
GPII-1903: Update project.pbxproj (new UIDs for NAPIJavaScriptFunctio…
christopher-rtf Mar 8, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
Copyright (c) 2012-2020 Members of the Raising the Floor Consortium.
All Rights Reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

3. Neither the name of the copyright holder nor the names of its contributors may
be used to endorse or promote products derived from this software without
specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
532 changes: 532 additions & 0 deletions MorphicMacOS.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

93 changes: 93 additions & 0 deletions MorphicMacOS/BridgeFunctions/NAPIAudioFunctions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
//
// NAPIAudioFunctions.swift
// Morphic support library for macOS
//
// Copyright © 2020 Raising the Floor -- US Inc. All rights reserved.
//
// The R&D leading to these results received funding from the
// Department of Education - Grant H421A150005 (GPII-APCP). However,
// these results do not necessarily represent the policy of the
// Department of Education, and you should not assume endorsement by the
// Federal Government.

class NAPIAudioFunctions {
// MARK: - Swift NAPI bridge setup

static func getFunctionsAsPropertyDescriptors(cNapiEnv: napi_env!) -> [napi_property_descriptor] {
var result: [napi_property_descriptor] = []

// getAudioVolume
result.append(NAPIProperty.createMethodProperty(cNapiEnv: cNapiEnv, name: "getAudioVolume", method: getAudioVolume).cNapiPropertyDescriptor)

// setAudioVolume
result.append(NAPIProperty.createMethodProperty(cNapiEnv: cNapiEnv, name: "setAudioVolume", method: setAudioVolume).cNapiPropertyDescriptor)

// getAudioMuteState
result.append(NAPIProperty.createMethodProperty(cNapiEnv: cNapiEnv, name: "getAudioMuteState", method: getAudioMuteState).cNapiPropertyDescriptor)

// setAudioMuteState
result.append(NAPIProperty.createMethodProperty(cNapiEnv: cNapiEnv, name: "setAudioMuteState", method: setAudioMuteState).cNapiPropertyDescriptor)

return result
}

// MARK: - Swift NAPI bridge functions

public static func getAudioVolume() throws -> Double {
guard let defaultAudioOutputDeviceId = MorphicAudio.getDefaultAudioDeviceId() else {
throw NAPISwiftBridgeJavaScriptThrowableError.error(message: "Could not get default audio device id")
}

// get the current volume
guard let volume = MorphicAudio.getVolume(for: defaultAudioOutputDeviceId) else {
throw NAPISwiftBridgeJavaScriptThrowableError.error(message: "Could not get volume of default audio device")
}

return Double(volume)
}

public static func getAudioMuteState() throws -> Bool {
guard let defaultAudioOutputDeviceId = MorphicAudio.getDefaultAudioDeviceId() else {
throw NAPISwiftBridgeJavaScriptThrowableError.error(message: "Could not get default audio device id")
}

// also get the mute state
guard let muteState = MorphicAudio.getMuteState(for: defaultAudioOutputDeviceId) else {
throw NAPISwiftBridgeJavaScriptThrowableError.error(message: "Could not get mute state of default audio device")
}

return muteState
}

public static func setAudioVolume(value: Double) throws {
guard let defaultAudioOutputDeviceId = MorphicAudio.getDefaultAudioDeviceId() else {
throw NAPISwiftBridgeJavaScriptThrowableError.error(message: "Could not get default audio device id")
}

do {
try MorphicAudio.setVolume(for: defaultAudioOutputDeviceId, volume: Float(value))
} catch MorphicAudio.MorphicAudioError.propertyUnavailable {
throw NAPISwiftBridgeJavaScriptThrowableError.error(message: "Could not find 'volume' property")
} catch MorphicAudio.MorphicAudioError.cannotSetProperty {
throw NAPISwiftBridgeJavaScriptThrowableError.error(message: "Could not set 'volume' property")
} catch MorphicAudio.MorphicAudioError.coreAudioError(let error) {
throw NAPISwiftBridgeJavaScriptThrowableError.error(message: "CoreAudio error: OSStatus(\(error))")
}
}

public static func setAudioMuteState(muteState: Bool) throws {
guard let defaultAudioOutputDeviceId = MorphicAudio.getDefaultAudioDeviceId() else {
throw NAPISwiftBridgeJavaScriptThrowableError.error(message: "Could not get default audio device id")
}

do {
try MorphicAudio.setMuteState(for: defaultAudioOutputDeviceId, muteState: muteState)
} catch MorphicAudio.MorphicAudioError.propertyUnavailable {
throw NAPISwiftBridgeJavaScriptThrowableError.error(message: "Could not find 'mute state' property")
} catch MorphicAudio.MorphicAudioError.cannotSetProperty {
throw NAPISwiftBridgeJavaScriptThrowableError.error(message: "Could not set 'mute state' property")
} catch MorphicAudio.MorphicAudioError.coreAudioError(let error) {
throw NAPISwiftBridgeJavaScriptThrowableError.error(message: "CoreAudio error: OSStatus(\(error))")
}
}
}
84 changes: 84 additions & 0 deletions MorphicMacOS/BridgeFunctions/NAPIDiskFunctions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
//
// NAPIDiskFunctions.swift
// Morphic support library for macOS
//
// Copyright © 2020 Raising the Floor -- US Inc. All rights reserved.
//
// The R&D leading to these results received funding from the
// Department of Education - Grant H421A150005 (GPII-APCP). However,
// these results do not necessarily represent the policy of the
// Department of Education, and you should not assume endorsement by the
// Federal Government.

class NAPIDiskFunctions {
// MARK: - Swift NAPI bridge setup

static func getFunctionsAsPropertyDescriptors(cNapiEnv: napi_env!) -> [napi_property_descriptor] {
var result: [napi_property_descriptor] = []

// getAllUsbDriveMountPaths
result.append(NAPIProperty.createMethodProperty(cNapiEnv: cNapiEnv, name: "getAllUsbDriveMountPaths", method: getAllUsbDriveMountPaths).cNapiPropertyDescriptor)

// openDirectories
result.append(NAPIProperty.createMethodProperty(cNapiEnv: cNapiEnv, name: "openDirectories", method: openDirectories).cNapiPropertyDescriptor)

// safelyEjectUsbDrives
result.append(NAPIProperty.createMethodProperty(cNapiEnv: cNapiEnv, name: "safelyEjectUsbDrives", method: safelyEjectUsbDrives).cNapiPropertyDescriptor)

return result
}

// MARK: - Swift NAPI bridge functions

public static func getAllUsbDriveMountPaths() throws -> [String] {
guard let result = MorphicDisk.getAllUsbDriveMountPaths() else {
throw NAPISwiftBridgeJavaScriptThrowableError.error(message: "Could not retrieve a list of all USB drive mount paths")
}

return result
}

public static func openDirectories(_ paths: [String]) {
// open directory paths using Finder
for path in paths {
MorphicDisk.openDirectory(path: path)
}
}

public static func safelyEjectUsbDrives(_ usbDriveMountingPaths: [String], _ callback: NAPIJavaScriptFunction?) throws {
let numberOfDisks = usbDriveMountingPaths.count
var numberOfDiskEjectsAttempted = 0
var failedMountPaths: [String] = []

// unmount and eject disk using disk arbitration
for mountPath in usbDriveMountingPaths {
do {
try MorphicDisk.ejectDisk(mountPath: mountPath) {
ejectedDiskPath, success in
//
numberOfDiskEjectsAttempted += 1
//
if success == true {
// we have ejected the disk at mount path: 'ejectedDiskPath'
} else {
// we failed to eject the disk at mount path: 'ejectedDiskPath'
failedMountPaths.append(mountPath)
}

if numberOfDiskEjectsAttempted == numberOfDisks {
// if a callback was provided, call it with success/failure (and an array of all mounting paths which failed)
if failedMountPaths.count == 0 {
callback?.call(args: [true, Array<String>?(nil)])
} else {
callback?.call(args: [false, failedMountPaths])
}
}
}
} catch MorphicDisk.EjectDiskError.volumeNotFound {
throw NAPISwiftBridgeJavaScriptThrowableError.error(message: "Failed to eject the disk at mount path: \(mountPath); volume was not found")
} catch MorphicDisk.EjectDiskError.otherError {
throw NAPISwiftBridgeJavaScriptThrowableError.error(message: "Failed to eject the disk at mount path: \(mountPath); misc. error encountered")
}
}
}
}
165 changes: 165 additions & 0 deletions MorphicMacOS/BridgeFunctions/NAPIDisplayFunctions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
//
// NAPIDiplayFunctions.swift
// Morphic support library for macOS
//
// Copyright © 2020 Raising the Floor -- US Inc. All rights reserved.
//
// The R&D leading to these results received funding from the
// Department of Education - Grant H421A150005 (GPII-APCP). However,
// these results do not necessarily represent the policy of the
// Department of Education, and you should not assume endorsement by the
// Federal Government.

class NAPIDisplayFunctions {
// MARK: - Swift NAPI bridge setup

static func getFunctionsAsPropertyDescriptors(cNapiEnv: napi_env!) -> [napi_property_descriptor] {
var result: [napi_property_descriptor] = []

// getDisplayModes
result.append(NAPIProperty.createMethodProperty(cNapiEnv: cNapiEnv, name: "getAllDisplayModes", method: getAllDisplayModes).cNapiPropertyDescriptor)

// getCurrentDisplayMode
result.append(NAPIProperty.createMethodProperty(cNapiEnv: cNapiEnv, name: "getCurrentDisplayMode", method: getCurrentDisplayMode).cNapiPropertyDescriptor)

// setCurrentDisplayMode
result.append(NAPIProperty.createMethodProperty(cNapiEnv: cNapiEnv, name: "setCurrentDisplayMode", method: setCurrentDisplayMode).cNapiPropertyDescriptor)

return result
}

// MARK: - Swift NAPI bridge functions

public struct NAPIDisplayMode: NAPIObjectCompatible {
let ioDisplayModeId: Double // Int32
let widthInPixels: Double // Int (Int32/Int64)
let heightInPixels: Double // Int (Int32/Int64)
let widthInVirtualPixels: Double // Int (Int32/Int64)
let heightInVirtualPixels: Double // Int (Int32/Int64)
let refreshRateInHertz: Double?
let isUsableForDesktopGui: Bool // NOTE: we can use this flag, in theory, to limit the resolutions we provide to user

static var NAPIPropertyCodingKeysAndTypes: [(propertyKey: CodingKey, type: NAPIValueType)] =
[
(propertyKey: CodingKeys.ioDisplayModeId, type: .number),
(propertyKey: CodingKeys.widthInPixels, type: .number),
(propertyKey: CodingKeys.heightInPixels, type: .number),
(propertyKey: CodingKeys.widthInVirtualPixels, type: .number),
(propertyKey: CodingKeys.heightInVirtualPixels, type: .number),
// TODO: we should probably change "type" to "wrappedType" (in the .nullable definition) and then maybe make it optional to write
(propertyKey: CodingKeys.refreshRateInHertz, type: .nullable(type: .number)),
(propertyKey: CodingKeys.isUsableForDesktopGui, type: .boolean)
]

init(displayMode: MorphicDisplay.DisplayMode) {
self.ioDisplayModeId = Double(exactly: displayMode.ioDisplayModeId)!
//
guard let widthInPixelsAsDouble = Double(exactly: displayMode.widthInPixels) else {
fatalError("widthInPixels cannot be represented as a 64-bit floating point value")
}
self.widthInPixels = widthInPixelsAsDouble
//
guard let heightInPixelsAsDouble = Double(exactly: displayMode.heightInPixels) else {
fatalError("heightInPixels cannot be represented as a 64-bit floating point value")
}
self.heightInPixels = heightInPixelsAsDouble
//
guard let widthInVirtualPixelsAsDouble = Double(exactly: displayMode.widthInVirtualPixels) else {
fatalError("widthInVirtualPixels cannot be represented as a 64-bit floating point value")
}
self.widthInVirtualPixels = widthInVirtualPixelsAsDouble
//
guard let heightInVirtualPixelsAsDouble = Double(exactly: displayMode.heightInVirtualPixels) else {
fatalError("heightInVirtualPixels cannot be represented as a 64-bit floating point value")
}
self.heightInVirtualPixels = heightInVirtualPixelsAsDouble
//
self.refreshRateInHertz = displayMode.refreshRateInHertz
//
self.isUsableForDesktopGui = displayMode.isUsableForDesktopGui
}
}

public static func getAllDisplayModes() throws -> [NAPIDisplayMode] {
guard let mainDisplayId = MorphicDisplay.getMainDisplayId() else {
throw NAPISwiftBridgeJavaScriptThrowableError.error(message: "Could not get main display id")
}

guard let allDisplayModes = MorphicDisplay.getAllDisplayModes(for: mainDisplayId) else {
throw NAPISwiftBridgeJavaScriptThrowableError.error(message: "Could not get list of display modes for main display")
}

var napiDisplayModes: [NAPIDisplayMode] = []
napiDisplayModes.reserveCapacity(allDisplayModes.count)
for displayMode in allDisplayModes {
let napiDisplayMode = NAPIDisplayMode(displayMode: displayMode)
napiDisplayModes.append(napiDisplayMode)
}

return napiDisplayModes
}

public static func getCurrentDisplayMode() throws -> NAPIDisplayMode {
guard let mainDisplayId = MorphicDisplay.getMainDisplayId() else {
throw NAPISwiftBridgeJavaScriptThrowableError.error(message: "Could not get main display id")
}

guard let currentDisplayMode = MorphicDisplay.getCurrentDisplayMode(for: mainDisplayId) else {
throw NAPISwiftBridgeJavaScriptThrowableError.error(message: "Could not get current display mode for main display")
}

let currentNapiDisplayMode = NAPIDisplayMode(displayMode: currentDisplayMode)
return currentNapiDisplayMode
}

public static func setCurrentDisplayMode(_ newNapiDisplayMode: NAPIDisplayMode) throws {
guard let mainDisplayId = MorphicDisplay.getMainDisplayId() else {
throw NAPISwiftBridgeJavaScriptThrowableError.error(message: "Could not get main display id")
}

guard let newDisplayMode = MorphicDisplay.DisplayMode(napiDisplayMode: newNapiDisplayMode) else {
throw NAPISwiftBridgeJavaScriptThrowableError.rangeError(message: "Argument 'newNapiDisplayMode' contains values which are out of range")
}

do {
try MorphicDisplay.setCurrentDisplayMode(for: mainDisplayId, to: newDisplayMode)
} catch MorphicDisplay.SetCurrentDisplayModeError.invalidDisplayMode {
throw NAPISwiftBridgeJavaScriptThrowableError.error(message: "Argument 'newNapiDisplayMode' is not a valid display mode")
} catch MorphicDisplay.SetCurrentDisplayModeError.otherError {
throw NAPISwiftBridgeJavaScriptThrowableError.error(message: "Could not set current display mode due to misc. error")
}
}
}
//
extension MorphicDisplay.DisplayMode {
init?(napiDisplayMode: NAPIDisplayFunctions.NAPIDisplayMode) {
guard let ioDisplayModeId = Int32(exactly: napiDisplayMode.ioDisplayModeId) else {
return nil
}
self.ioDisplayModeId = ioDisplayModeId
//
guard let widthInPixelsAsInt = Int(exactly: napiDisplayMode.widthInPixels) else {
return nil
}
self.widthInPixels = widthInPixelsAsInt
//
guard let heightInPixelsAsInt = Int(exactly: napiDisplayMode.heightInPixels) else {
return nil
}
self.heightInPixels = heightInPixelsAsInt
//
guard let widthInVirtualPixelsAsInt = Int(exactly: napiDisplayMode.widthInVirtualPixels) else {
return nil
}
self.widthInVirtualPixels = widthInVirtualPixelsAsInt
//
guard let heightInVirtualPixelsAsInt = Int(exactly: napiDisplayMode.heightInVirtualPixels) else {
return nil
}
self.heightInVirtualPixels = heightInVirtualPixelsAsInt
//
self.refreshRateInHertz = napiDisplayMode.refreshRateInHertz
//
self.isUsableForDesktopGui = napiDisplayMode.isUsableForDesktopGui
}
}
Loading