Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
30 changes: 15 additions & 15 deletions CameraController/Vendored/MoveApplication.swift
Original file line number Diff line number Diff line change
Expand Up @@ -66,21 +66,21 @@ class LetsMove: NSObject {

// Path of the bundle
let bundlePath = Bundle.main.bundlePath
let bundleNameURL = URL(string: bundlePath)
let bundleNameURL = URL(fileURLWithPath: bundlePath)

// Skip if the application is already in some Applications folder
guard !isInApplicationsFolder(bundleNameURL!) else { return }
guard !isInApplicationsFolder(bundleNameURL) else { return }

// Since we are good to go, get the preferred installation directory.
let (applicationsDirectory, installToUserApplications) = preferredInstallLocation()
let bundleName = bundleNameURL!.lastPathComponent
let bundleName = bundleNameURL.lastPathComponent
let destinationURL = applicationsDirectory!.appendingPathComponent(bundleName)

// Check if we need admin password to write to the Applications directory
// Check if the destination bundle is already there but not writable
let isWritableFileSrc = fileManager.isWritableFile(atPath: applicationsDirectory!.absoluteString)
let isFileExists = fileManager.fileExists(atPath: destinationURL.absoluteString)
let isWritableFileDst = fileManager.isWritableFile(atPath: destinationURL.absoluteString)
let isWritableFileSrc = fileManager.isWritableFile(atPath: applicationsDirectory!.path)
let isFileExists = fileManager.fileExists(atPath: destinationURL.path)
let isWritableFileDst = fileManager.isWritableFile(atPath: destinationURL.path)

let isNeedAuthorization = !isWritableFileSrc || (isFileExists && !isWritableFileDst)

Expand All @@ -96,7 +96,7 @@ class LetsMove: NSObject {
print("INFO -- Moving myself to the Applications folder")

if isNeedAuthorization {
if !authorizedInstall(srcPath: bundlePath, dstPath: destinationURL.absoluteString) {
if !authorizedInstall(srcPath: bundlePath, dstPath: destinationURL.path) {
print("ERROR -- Could not copy myself to /Applications with authorization")
return
}
Expand All @@ -113,7 +113,7 @@ class LetsMove: NSObject {

// Relaunch.
print("relaunch")
relaunch(destinationPath: destinationURL.absoluteString)
relaunch(destinationPath: destinationURL.path)
exit(0)
} else if alert.suppressionButton!.state == .on {
// Save the alert suppress preference if checked
Expand Down Expand Up @@ -162,17 +162,17 @@ class LetsMove: NSObject {
_ bundleName: String,
_ moveStrings: LetsMove.MoveStrings) -> Bool {
// If a copy already exists in the Applications folder, put it in the Trash
if fileManager.fileExists(atPath: destinationURL.absoluteString) {
if fileManager.fileExists(atPath: destinationURL.path) {

// But first, make sure that it's not running
if isApplicationAtPathRunning(path: destinationURL.absoluteString) {
if isApplicationAtPathRunning(path: destinationURL.path) {
// Give the running app focus and terminate myself
print("INFO -- Switching to an already running version")
Process.launchedProcess(launchPath: "/usr/bin/open", arguments:
[destinationURL.absoluteString]).waitUntilExit()
[destinationURL.path]).waitUntilExit()
exit(0)
} else {
let path = applicationsDirectory!.appendingPathComponent(bundleName).absoluteString
let path = applicationsDirectory!.appendingPathComponent(bundleName).path
if !trash(path: path) {
let alert = NSAlert()
alert.messageText = moveStrings.couldNotMove
Expand All @@ -182,8 +182,8 @@ class LetsMove: NSObject {
}
}

if !copyBundle(srcPath: bundlePath, dstPath: destinationURL.absoluteString) {
print("Could not copy myself to \(destinationURL.absoluteString)")
if !copyBundle(srcPath: bundlePath, dstPath: destinationURL.path) {
print("Could not copy myself to \(destinationURL.path)")
return false
}

Expand Down Expand Up @@ -214,7 +214,7 @@ class LetsMove: NSObject {
if appDir == nil {
let localLocations = NSSearchPathForDirectoriesInDomains(.applicationDirectory, .localDomainMask, true)
if let last = localLocations.last {
appDir = URL(string: last)
appDir = URL(fileURLWithPath: last)
}
}
return (appDir, isUserDir)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ struct GenericControl: View {
@Binding var auto: Bool
@State var step: Float
@State var range: ClosedRange<Float>
let defaultValue: Float

let title: String
let imageName: String
Expand All @@ -22,18 +23,34 @@ struct GenericControl: View {
init(value: Binding<Float>,
step: Float,
range: ClosedRange<Float>,
defaultValue: Float,
title: String,
imageName: String,
auto: Binding<Bool>?) {
self._value = value
self.step = step
self.range = range
self.defaultValue = defaultValue
self.title = title
self.imageName = imageName
self.hasAuto = auto != nil
self._auto = auto ?? .constant(false)
}

var sliderValue: Binding<Float> {
Binding(get: {
value
}, set: { newValue in
// Moving the slider while in auto switches the control to manual
if hasAuto, auto {
withAnimation {
auto = false
}
}
value = newValue
})
}

var body: some View {
SectionView {
SectionTitle(title: title,
Expand All @@ -54,10 +71,10 @@ struct GenericControl: View {
Toggle(isOn: .constant(false))
.hidden()
}
Slider(value: $value,
Slider(value: sliderValue,
step: step,
sliderRange: range)
.disabled($auto.wrappedValue)
sliderRange: range,
defaultValue: defaultValue)
}
}
}
Expand Down
42 changes: 41 additions & 1 deletion CameraController/Views/Common/ControlComponents/Slider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,19 @@
//

import SwiftUI
import AppKit

struct Slider: View {
@Binding var value: Float
@Environment(\.isEnabled) var isEnabled: Bool

@State var lastCoordinateValue: Float = 0.0
@State private var isDragging = false
@State private var isResetClick = false
@State private var lastThumbClick: Date?
let sliderRange: ClosedRange<Float>
let step: Float
let defaultValue: Float?
let minTrackColor: Color = Constants.Colors.accentColor
let maxTrackColor: Color = Constants.Colors.sliderBackground
let disabledColor: Color = Constants.Colors.disabledSlider
Expand All @@ -23,10 +28,12 @@ struct Slider: View {

init(value: Binding<Float>,
step: Float = 1,
sliderRange: ClosedRange<Float> = 0...100) {
sliderRange: ClosedRange<Float> = 0...100,
defaultValue: Float? = nil) {
_value = value
self.step = step
self.sliderRange = sliderRange
self.defaultValue = defaultValue
self.lastCoordinateValue = lastCoordinateValue
}

Expand Down Expand Up @@ -68,6 +75,19 @@ struct Slider: View {
.gesture(
DragGesture(minimumDistance: 0, coordinateSpace: .local)
.onChanged { value in
if !isDragging {
isDragging = true
isResetClick = isThumbDoubleClick(at: value.startLocation.x,
thumbStart: CGFloat(sliderVal),
thumbWidth: thumbWidth)
}
// Double-clicking the thumb resets to the default instead of dragging
if isResetClick {
if let defaultValue = defaultValue {
self.value = defaultValue
}
return
}
let percentage = min(
max(
Float(value.location.x-thumbWidth/2) / Float(geoReader.size.width-thumbWidth),
Expand All @@ -77,12 +97,32 @@ struct Slider: View {
let tmpValue = percentageToValueInRange(percentage, sliderRange)
self.value = tmpValue - tmpValue.truncatingRemainder(dividingBy: step)
}
.onEnded { _ in
isDragging = false
}
)
.allowsHitTesting(self.isEnabled)
}
.frame(height: 22)
}

private func isThumbDoubleClick(at location: CGFloat, thumbStart: CGFloat, thumbWidth: CGFloat) -> Bool {
guard defaultValue != nil,
(thumbStart...thumbStart + thumbWidth).contains(location) else {
lastThumbClick = nil
return false
}

let now = Date()
if let lastThumbClick = lastThumbClick,
now.timeIntervalSince(lastThumbClick) <= NSEvent.doubleClickInterval {
self.lastThumbClick = nil
return true
}
lastThumbClick = now
return false
}

private func percentageToValueInRange(_ percentage: Float, _ range: ClosedRange<Float>) -> Float {
return range.lowerBound + (range.upperBound - range.lowerBound) * percentage
}
Expand Down
69 changes: 47 additions & 22 deletions CameraController/Views/Preview/CameraPreviewInternal.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// CameraPrreviewInternal.swift
// CameraPreviewInternal.swift
// CameraController
//
// Created by Itay Brenner on 7/21/20.
Expand All @@ -15,25 +15,38 @@ final class CameraPreviewInternal: NSView {
private var captureSession: AVCaptureSession
private var previewLayer: AVCaptureVideoPreviewLayer!
private var captureInput: AVCaptureInput?

// Serial queue to handle all blocking AVCaptureSession start/stop and device lock operations
private let sessionQueue = DispatchQueue(label: "com.itaysoft.CameraController.sessionQueue", qos: .userInitiated)

init(frame frameRect: NSRect, device: AVCaptureDevice?) {
captureDevice = device
captureSession = AVCaptureSession()

super.init(frame: frameRect)

// 1. Enable layer-backing for this NSView
self.wantsLayer = true

setupPreviewLayer(captureSession)

// 2. Add the sublayer once here instead of during layout
if let layer = self.layer {
layer.addSublayer(previewLayer)
}

Task {
configureDevice(device)
// lock configuration to keep device.activeFormat
do {
try captureDevice?.lockForConfiguration()
captureSession.startRunning()
captureDevice?.unlockForConfiguration()
} catch {
configureDevice(device)

let session = captureSession
let dev = captureDevice
sessionQueue.async {
do {
try dev?.lockForConfiguration()
session.startRunning()
dev?.unlockForConfiguration()
} catch {
// Handle error.
}
}
}

NotificationCenter.default.addObserver(self,
Expand Down Expand Up @@ -67,27 +80,36 @@ final class CameraPreviewInternal: NSView {

override func layout() {
super.layout()
// 3. Only update the bounds here. Do not add or manipulate layers.
previewLayer.frame = bounds
layer?.addSublayer(previewLayer)
}

func stopRunning() {
if captureSession.isRunning {
captureSession.stopRunning()
let session = captureSession
sessionQueue.async {
if session.isRunning {
session.stopRunning()
}
}
}

func updateCamera(_ cam: AVCaptureDevice?) {
if captureDevice != cam {
captureSession.stopRunning()
let session = captureSession
sessionQueue.async {
if session.isRunning {
session.stopRunning()
}
}

Task {
configureDevice(cam)
// lock configuration to keep device.activeFormat
configureDevice(cam)

let dev = captureDevice
sessionQueue.async {
do {
try captureDevice?.lockForConfiguration()
captureSession.startRunning()
captureDevice?.unlockForConfiguration()
try dev?.lockForConfiguration()
session.startRunning()
dev?.unlockForConfiguration()
} catch {
// Handle error.
}
Expand Down Expand Up @@ -127,8 +149,11 @@ final class CameraPreviewInternal: NSView {

@objc
func windowOpen() {
if !captureSession.isRunning {
captureSession.startRunning()
let session = captureSession
sessionQueue.async {
if !session.isRunning {
session.startRunning()
}
}
}
}
Expand Down
1 change: 1 addition & 0 deletions CameraController/Views/Settings/Advanced/FocusView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ struct FocusView: View {
GenericControl(value: $focusAbsolute.sliderValue,
step: focusAbsolute.resolution,
range: focusAbsolute.minimum...focusAbsolute.maximum,
defaultValue: focusAbsolute.defaultValue,
title: "Focus",
imageName: "camera.aperture",
auto: $focusAuto.isEnabled)
Expand Down
6 changes: 4 additions & 2 deletions CameraController/Views/Settings/Advanced/PanTiltView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ struct PanTiltView: View {
.hidden()
Slider(value: $panTiltAbsolute.sliderValue1,
step: panTiltAbsolute.resolution1,
sliderRange: panTiltAbsolute.minimum1...panTiltAbsolute.maximum1)
sliderRange: panTiltAbsolute.minimum1...panTiltAbsolute.maximum1,
defaultValue: panTiltAbsolute.defaultValue1)
}
}

Expand All @@ -37,7 +38,8 @@ struct PanTiltView: View {
.hidden()
Slider(value: $panTiltAbsolute.sliderValue2,
step: panTiltAbsolute.resolution2,
sliderRange: panTiltAbsolute.minimum2...panTiltAbsolute.maximum2)
sliderRange: panTiltAbsolute.minimum2...panTiltAbsolute.maximum2,
defaultValue: panTiltAbsolute.defaultValue2)
}
}
}
Expand Down
1 change: 1 addition & 0 deletions CameraController/Views/Settings/Advanced/RollView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ struct RollView: View {
GenericControl(value: $rollAbsolute.sliderValue,
step: rollAbsolute.resolution,
range: rollAbsolute.minimum...rollAbsolute.maximum,
defaultValue: rollAbsolute.defaultValue,
title: "Roll",
imageName: "arrow.counterclockwise",
auto: nil)
Expand Down
1 change: 1 addition & 0 deletions CameraController/Views/Settings/Advanced/ZoomView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ struct ZoomView: View {
GenericControl(value: $zoomAbsolute.sliderValue,
step: zoomAbsolute.resolution,
range: zoomAbsolute.minimum...zoomAbsolute.maximum,
defaultValue: zoomAbsolute.defaultValue,
title: "Zoom",
imageName: "plus.magnifyingglass",
auto: nil)
Expand Down
1 change: 1 addition & 0 deletions CameraController/Views/Settings/Basic/BrightnessView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ struct BrightnessView: View {
GenericControl(value: $brightness.sliderValue,
step: brightness.resolution,
range: brightness.minimum...brightness.maximum,
defaultValue: brightness.defaultValue,
title: "Brightness",
imageName: "sun.max.fill",
auto: nil)
Expand Down
Loading