diff --git a/Quick Camera/Base.lproj/MainMenu.xib b/Quick Camera/Base.lproj/MainMenu.xib
index 62b7886..0536a79 100644
--- a/Quick Camera/Base.lproj/MainMenu.xib
+++ b/Quick Camera/Base.lproj/MainMenu.xib
@@ -27,7 +27,11 @@
-
+
diff --git a/Quick Camera/QCAppDelegate.swift b/Quick Camera/QCAppDelegate.swift
index 7730ac3..2743b63 100644
--- a/Quick Camera/QCAppDelegate.swift
+++ b/Quick Camera/QCAppDelegate.swift
@@ -2,6 +2,62 @@ import AVFoundation
import AVKit
import Cocoa
+private let kShowResolutionOptions = "showCameraResolutionOptions"
+
+// MARK: - Preferences
+
+class QCPreferencesViewController: NSViewController {
+
+ var onChange: (() -> Void)?
+
+ private let checkbox = NSButton(
+ checkboxWithTitle: "Show camera resolution options",
+ target: nil,
+ action: nil
+ )
+
+ override func loadView() {
+ let container = NSView(frame: NSRect(x: 0, y: 0, width: 340, height: 80))
+ checkbox.translatesAutoresizingMaskIntoConstraints = false
+ checkbox.target = self
+ checkbox.action = #selector(checkboxChanged(_:))
+ checkbox.state = UserDefaults.standard.bool(forKey: kShowResolutionOptions) ? .on : .off
+ container.addSubview(checkbox)
+ NSLayoutConstraint.activate([
+ checkbox.centerXAnchor.constraint(equalTo: container.centerXAnchor),
+ checkbox.centerYAnchor.constraint(equalTo: container.centerYAnchor),
+ ])
+ self.view = container
+ }
+
+ @objc private func checkboxChanged(_ sender: NSButton) {
+ UserDefaults.standard.set(sender.state == .on, forKey: kShowResolutionOptions)
+ onChange?()
+ }
+}
+
+class QCPreferencesWindowController: NSWindowController {
+
+ convenience init() {
+ let vc = QCPreferencesViewController()
+ let window = NSWindow(contentViewController: vc)
+ window.title = "Preferences"
+ window.styleMask = [.titled, .closable]
+ window.setContentSize(NSSize(width: 340, height: 80))
+ window.center()
+ self.init(window: window)
+ }
+
+ var preferencesViewController: QCPreferencesViewController? {
+ return contentViewController as? QCPreferencesViewController
+ }
+
+ override func showWindow(_ sender: Any?) {
+ window?.center()
+ super.showWindow(sender)
+ }
+}
+
// MARK: - QCAppDelegate Class
@NSApplicationMain
class QCAppDelegate: NSObject, NSApplicationDelegate, QCUsbWatcherDelegate {
@@ -112,9 +168,16 @@ class QCAppDelegate: NSObject, NSApplicationDelegate, QCUsbWatcherDelegate {
if deviceIndex < 9 {
deviceMenuItem.keyEquivalent = String(deviceIndex + 1)
}
+
+ // Add resolution change submenu only if enabled in Preferences
+ if UserDefaults.standard.bool(forKey: kShowResolutionOptions) {
+ addResolutionsTo(menuItem: deviceMenuItem, forDevice: device)
+ }
+
deviceMenu.addItem(deviceMenuItem)
deviceIndex += 1
}
+
selectSourceMenu.submenu = deviceMenu
}
@@ -472,6 +535,104 @@ class QCAppDelegate: NSObject, NSApplicationDelegate, QCUsbWatcherDelegate {
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
true
}
+
+ // MARK: Resolutions
+
+ var selectedSourceResolution: Int = -1
+ var preferencesWindowController: QCPreferencesWindowController?
+
+ // MARK: Preferences
+
+ @IBAction func openPreferences(_ sender: Any?) {
+ if preferencesWindowController == nil {
+ preferencesWindowController = QCPreferencesWindowController()
+ // Rebuild the device menu immediately whenever the checkbox is toggled
+ preferencesWindowController?.preferencesViewController?.onChange = { [weak self] in
+ self?.detectVideoDevices()
+ }
+ }
+ preferencesWindowController?.showWindow(self)
+ NSApp.activate(ignoringOtherApps: true)
+ }
+
+ @objc func sourceResolutionsMenuChanged(_ sender: NSMenuItem) {
+ for menuItem: NSMenuItem in sender.parent!.submenu!.items {
+ menuItem.state = NSControl.StateValue.off;
+ }
+ sender.state = NSControl.StateValue.on;
+
+ /// should have a pair of device to res
+ self.selectedSourceResolution = sender.representedObject as! Int
+
+ let deviceMenuItem = sender.parent!
+ for menuItem: NSMenuItem in deviceMenuItem.parent!.submenu!.items {
+ menuItem.state = NSControl.StateValue.off;
+ }
+ deviceMenuItem.state = NSControl.StateValue.on
+
+ self.selectedDeviceIndex = deviceMenuItem.representedObject as! Int
+
+ /// then select the device and resolution
+ self.startCaptureWithVideoDevice(defaultDevice: self.selectedDeviceIndex)
+ self.applyResolutionToDevice()
+ }
+
+ func applyResolutionToDevice()
+ {
+ let device = (self.captureSession.inputs[0] as! AVCaptureDeviceInput).device
+ try! device.lockForConfiguration()
+
+ let format = device.formats[selectedSourceResolution]
+ device.activeFormat = format
+
+ let maxFrameRateDuration = format.videoSupportedFrameRateRanges.reduce(CMTime.positiveInfinity) { (res, e) -> CMTime in
+ CMTimeMinimum(res, e.minFrameDuration)
+ }
+ device.activeVideoMinFrameDuration = maxFrameRateDuration
+ device.unlockForConfiguration()
+ }
+
+ // MARK - source resolutions
+ func addResolutionsTo(menuItem: NSMenuItem, forDevice: AVCaptureDevice) {
+
+ let selectedDevice = forDevice
+ let resolutionsMenu = NSMenu();
+
+ var resIndex = 0;
+
+
+ for res in selectedDevice.formats {
+
+ let menuTitle = descriptionFor(resolution: res)
+
+ //if not already in the menu
+ if(resolutionsMenu.indexOfItem(withTitle: menuTitle) == -1) {
+
+ let deviceMenuItem = NSMenuItem(title: menuTitle, action: #selector(sourceResolutionsMenuChanged), keyEquivalent: "")
+ deviceMenuItem.target = self;
+ deviceMenuItem.representedObject = resIndex;
+
+ if(selectedDevice.activeFormat == res) {
+ deviceMenuItem.state = NSControl.StateValue.on;
+ self.selectedSourceResolution = resIndex
+ }
+
+ resolutionsMenu.addItem(deviceMenuItem);
+
+ menuItem.submenu = resolutionsMenu
+ }
+ resIndex += 1;
+
+
+ }
+ }
+
+ private func descriptionFor(resolution: AVCaptureDevice.Format) -> String {
+ let dims = CMVideoFormatDescriptionGetDimensions(resolution.formatDescription)
+ return "\(dims.width)x\(dims.height)"
+ }
+
+
}
// MARK: - Helper Functions