OSDN Git Service

public release
authorHiromichi Matsushima <hylom@imac4k.local>
Sun, 17 Mar 2019 08:25:38 +0000 (17:25 +0900)
committerHiromichi Matsushima <hylom@iMac4k.local>
Sun, 17 Mar 2019 08:25:38 +0000 (17:25 +0900)
26 files changed:
OrbitCapture.xcodeproj/project.pbxproj
OrbitCapture.xcodeproj/project.xcworkspace/contents.xcworkspacedata
OrbitCapture.xcodeproj/xcuserdata/hylom.xcuserdatad/xcschemes/xcschememanagement.plist
OrbitCapture/AppDelegate.swift
OrbitCapture/Assets.xcassets/AppIcon.appiconset/Contents.json
OrbitCapture/Assets.xcassets/AppIcon.appiconset/orbit1024.png [new file with mode: 0644]
OrbitCapture/Assets.xcassets/AppIcon.appiconset/orbit128.png [new file with mode: 0644]
OrbitCapture/Assets.xcassets/AppIcon.appiconset/orbit16.png [new file with mode: 0644]
OrbitCapture/Assets.xcassets/AppIcon.appiconset/orbit256-1.png [new file with mode: 0644]
OrbitCapture/Assets.xcassets/AppIcon.appiconset/orbit256.png [new file with mode: 0644]
OrbitCapture/Assets.xcassets/AppIcon.appiconset/orbit32-1.png [new file with mode: 0644]
OrbitCapture/Assets.xcassets/AppIcon.appiconset/orbit32.png [new file with mode: 0644]
OrbitCapture/Assets.xcassets/AppIcon.appiconset/orbit512-1.png [new file with mode: 0644]
OrbitCapture/Assets.xcassets/AppIcon.appiconset/orbit512.png [new file with mode: 0644]
OrbitCapture/Assets.xcassets/AppIcon.appiconset/orbit64.png [new file with mode: 0644]
OrbitCapture/Base.lproj/MainMenu.xib
OrbitCapture/Base.lproj/MainWindow.xib [new file with mode: 0644]
OrbitCapture/LICENSE.txt [new file with mode: 0644]
OrbitCapture/MainWindowController.swift [new file with mode: 0644]
OrbitCapture/OrbitCapture-Bridging-Header.h [new file with mode: 0644]
OrbitCapture/OrbitCapture.entitlements
OrbitCapture/README.md [new file with mode: 0644]
OrbitCapture/UVCCameraControl.h [new file with mode: 0644]
OrbitCapture/UVCCameraControl.m [new file with mode: 0644]
orbitcam.psd [new file with mode: 0644]
orbitcam_small.psd [new file with mode: 0644]

index e22cb1b..6eb506a 100644 (file)
                EF1ABB6722283897006DB8EA /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = EF1ABB6522283897006DB8EA /* MainMenu.xib */; };
                EF1ABB7322283897006DB8EA /* OrbitCaptureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF1ABB7222283897006DB8EA /* OrbitCaptureTests.swift */; };
                EF1ABB7E22283897006DB8EA /* OrbitCaptureUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF1ABB7D22283897006DB8EA /* OrbitCaptureUITests.swift */; };
+               EF1ABB8D222839B4006DB8EA /* AVKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EF1ABB8C222839B4006DB8EA /* AVKit.framework */; };
+               EF7DFED7223E3A6100C2A0F3 /* LICENSE.txt in Resources */ = {isa = PBXBuildFile; fileRef = EF7DFED6223E3A6100C2A0F3 /* LICENSE.txt */; };
+               EFA458DB22299BE00062EBDC /* UVCCameraControl.m in Sources */ = {isa = PBXBuildFile; fileRef = EFA458DA22299BE00062EBDC /* UVCCameraControl.m */; };
+               EFC01946223E18A9007AA427 /* MainWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = EFC01944223E18A9007AA427 /* MainWindow.xib */; };
+               EFE56882222D60C8005EB66E /* MainWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EFE56881222D60C8005EB66E /* MainWindowController.swift */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXContainerItemProxy section */
                EF1ABB7922283897006DB8EA /* OrbitCaptureUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = OrbitCaptureUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
                EF1ABB7D22283897006DB8EA /* OrbitCaptureUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrbitCaptureUITests.swift; sourceTree = "<group>"; };
                EF1ABB7F22283897006DB8EA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
+               EF1ABB8C222839B4006DB8EA /* AVKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVKit.framework; path = System/Library/Frameworks/AVKit.framework; sourceTree = SDKROOT; };
+               EF7DFED5223E379D00C2A0F3 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
+               EF7DFED6223E3A6100C2A0F3 /* LICENSE.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE.txt; sourceTree = "<group>"; };
+               EFA458D822299BDF0062EBDC /* OrbitCapture-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OrbitCapture-Bridging-Header.h"; sourceTree = "<group>"; };
+               EFA458D922299BE00062EBDC /* UVCCameraControl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UVCCameraControl.h; sourceTree = "<group>"; };
+               EFA458DA22299BE00062EBDC /* UVCCameraControl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UVCCameraControl.m; sourceTree = "<group>"; };
+               EFC01945223E18A9007AA427 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainWindow.xib; sourceTree = "<group>"; };
+               EFE56881222D60C8005EB66E /* MainWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainWindowController.swift; sourceTree = "<group>"; };
 /* End PBXFileReference section */
 
 /* Begin PBXFrameworksBuildPhase section */
@@ -51,6 +64,7 @@
                        isa = PBXFrameworksBuildPhase;
                        buildActionMask = 2147483647;
                        files = (
+                               EF1ABB8D222839B4006DB8EA /* AVKit.framework in Frameworks */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
@@ -78,6 +92,7 @@
                                EF1ABB7122283897006DB8EA /* OrbitCaptureTests */,
                                EF1ABB7C22283897006DB8EA /* OrbitCaptureUITests */,
                                EF1ABB5F22283896006DB8EA /* Products */,
+                               EF1ABB8B222839B4006DB8EA /* Frameworks */,
                        );
                        sourceTree = "<group>";
                };
                EF1ABB6022283896006DB8EA /* OrbitCapture */ = {
                        isa = PBXGroup;
                        children = (
+                               EFA458D922299BE00062EBDC /* UVCCameraControl.h */,
+                               EF7DFED6223E3A6100C2A0F3 /* LICENSE.txt */,
+                               EFA458DA22299BE00062EBDC /* UVCCameraControl.m */,
+                               EF7DFED5223E379D00C2A0F3 /* README.md */,
+                               EF1ABB6522283897006DB8EA /* MainMenu.xib */,
+                               EFC01944223E18A9007AA427 /* MainWindow.xib */,
+                               EFE56881222D60C8005EB66E /* MainWindowController.swift */,
                                EF1ABB6122283897006DB8EA /* AppDelegate.swift */,
                                EF1ABB6322283897006DB8EA /* Assets.xcassets */,
-                               EF1ABB6522283897006DB8EA /* MainMenu.xib */,
                                EF1ABB6822283897006DB8EA /* Info.plist */,
                                EF1ABB6922283897006DB8EA /* OrbitCapture.entitlements */,
+                               EFA458D822299BDF0062EBDC /* OrbitCapture-Bridging-Header.h */,
                        );
                        path = OrbitCapture;
                        sourceTree = "<group>";
                        path = OrbitCaptureUITests;
                        sourceTree = "<group>";
                };
+               EF1ABB8B222839B4006DB8EA /* Frameworks */ = {
+                       isa = PBXGroup;
+                       children = (
+                               EF1ABB8C222839B4006DB8EA /* AVKit.framework */,
+                       );
+                       name = Frameworks;
+                       sourceTree = "<group>";
+               };
 /* End PBXGroup section */
 
 /* Begin PBXNativeTarget section */
                                EF1ABB5A22283896006DB8EA /* Sources */,
                                EF1ABB5B22283896006DB8EA /* Frameworks */,
                                EF1ABB5C22283896006DB8EA /* Resources */,
+                               EFE20BEE222C474B00D9CD46 /* ShellScript */,
                        );
                        buildRules = (
                        );
                                TargetAttributes = {
                                        EF1ABB5D22283896006DB8EA = {
                                                CreatedOnToolsVersion = 9.2;
+                                               LastSwiftMigration = 0920;
                                                ProvisioningStyle = Automatic;
+                                               SystemCapabilities = {
+                                                       com.apple.Sandbox = {
+                                                               enabled = 0;
+                                                       };
+                                               };
                                        };
                                        EF1ABB6D22283897006DB8EA = {
                                                CreatedOnToolsVersion = 9.2;
                        isa = PBXResourcesBuildPhase;
                        buildActionMask = 2147483647;
                        files = (
+                               EF7DFED7223E3A6100C2A0F3 /* LICENSE.txt in Resources */,
                                EF1ABB6422283897006DB8EA /* Assets.xcassets in Resources */,
                                EF1ABB6722283897006DB8EA /* MainMenu.xib in Resources */,
+                               EFC01946223E18A9007AA427 /* MainWindow.xib in Resources */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
                };
 /* End PBXResourcesBuildPhase section */
 
+/* Begin PBXShellScriptBuildPhase section */
+               EFE20BEE222C474B00D9CD46 /* ShellScript */ = {
+                       isa = PBXShellScriptBuildPhase;
+                       buildActionMask = 2147483647;
+                       files = (
+                       );
+                       inputPaths = (
+                       );
+                       outputPaths = (
+                       );
+                       runOnlyForDeploymentPostprocessing = 0;
+                       shellPath = /bin/sh;
+                       shellScript = "set -ex\n\n[ \"$ACTION\" = build ] || exit 0\n[ \"$BUILD_VARIANTS\" = \"normal\" ] || exit 0\n[ \"$CONFIGURATION\" = \"Release\" ] || exit 0\n\ndir=\"$TEMP_FILES_DIR/disk\"\ndmg=\"$HOME/Desktop/$PROJECT_NAME.dmg\"\n\nrm -rf \"$dir\"\nmkdir \"$dir\"\ncp -R \"$BUILT_PRODUCTS_DIR/$PROJECT_NAME.app\" \"$dir\"\nln -s \"/Applications\" \"$dir/Applications\"\nrm -f \"$dmg\"\nhdiutil create -srcfolder \"$dir\" -volname \"$PROJECT_NAME\" \"$dmg\"\nrm -rf \"$dir\"";
+               };
+/* End PBXShellScriptBuildPhase section */
+
 /* Begin PBXSourcesBuildPhase section */
                EF1ABB5A22283896006DB8EA /* Sources */ = {
                        isa = PBXSourcesBuildPhase;
                        buildActionMask = 2147483647;
                        files = (
+                               EFA458DB22299BE00062EBDC /* UVCCameraControl.m in Sources */,
+                               EFE56882222D60C8005EB66E /* MainWindowController.swift in Sources */,
                                EF1ABB6222283897006DB8EA /* AppDelegate.swift in Sources */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                        name = MainMenu.xib;
                        sourceTree = "<group>";
                };
+               EFC01944223E18A9007AA427 /* MainWindow.xib */ = {
+                       isa = PBXVariantGroup;
+                       children = (
+                               EFC01945223E18A9007AA427 /* Base */,
+                       );
+                       name = MainWindow.xib;
+                       sourceTree = "<group>";
+               };
 /* End PBXVariantGroup section */
 
 /* Begin XCBuildConfiguration section */
                        isa = XCBuildConfiguration;
                        buildSettings = {
                                ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
-                               CODE_SIGN_ENTITLEMENTS = OrbitCapture/OrbitCapture.entitlements;
+                               CLANG_ENABLE_MODULES = YES;
                                CODE_SIGN_STYLE = Automatic;
                                COMBINE_HIDPI_IMAGES = YES;
                                DEVELOPMENT_TEAM = X4ZJ46Q747;
                                LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
                                PRODUCT_BUNDLE_IDENTIFIER = hylom.OrbitCapture;
                                PRODUCT_NAME = "$(TARGET_NAME)";
+                               SWIFT_INSTALL_OBJC_HEADER = NO;
+                               SWIFT_OBJC_BRIDGING_HEADER = "OrbitCapture/OrbitCapture-Bridging-Header.h";
+                               SWIFT_OBJC_INTERFACE_HEADER_NAME = "";
+                               SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+                               SWIFT_PRECOMPILE_BRIDGING_HEADER = NO;
                                SWIFT_VERSION = 4.0;
                        };
                        name = Debug;
                        isa = XCBuildConfiguration;
                        buildSettings = {
                                ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
-                               CODE_SIGN_ENTITLEMENTS = OrbitCapture/OrbitCapture.entitlements;
+                               CLANG_ENABLE_MODULES = YES;
                                CODE_SIGN_STYLE = Automatic;
                                COMBINE_HIDPI_IMAGES = YES;
                                DEVELOPMENT_TEAM = X4ZJ46Q747;
                                LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
                                PRODUCT_BUNDLE_IDENTIFIER = hylom.OrbitCapture;
                                PRODUCT_NAME = "$(TARGET_NAME)";
+                               SWIFT_INSTALL_OBJC_HEADER = NO;
+                               SWIFT_OBJC_BRIDGING_HEADER = "OrbitCapture/OrbitCapture-Bridging-Header.h";
+                               SWIFT_OBJC_INTERFACE_HEADER_NAME = "";
+                               SWIFT_PRECOMPILE_BRIDGING_HEADER = NO;
                                SWIFT_VERSION = 4.0;
                        };
                        name = Release;
index 593f351..6460974 100644 (file)
@@ -2,6 +2,12 @@
 <Workspace
    version = "1.0">
    <FileRef
-      location = "self:OrbitCapture.xcodeproj">
+      location = "group:/Users/hylom/Documents/Dev/OrbitCapture/OrbitCapture/UVCCameraControl.h">
+   </FileRef>
+   <FileRef
+      location = "group:/Users/hylom/Documents/Dev/OrbitCapture/OrbitCapture/UVCCameraControl.m">
+   </FileRef>
+   <FileRef
+      location = "self:">
    </FileRef>
 </Workspace>
index a3a2e78..d42e0b3 100644 (file)
                        <integer>0</integer>
                </dict>
        </dict>
+       <key>SuppressBuildableAutocreation</key>
+       <dict>
+               <key>EF1ABB5D22283896006DB8EA</key>
+               <dict>
+                       <key>primary</key>
+                       <true/>
+               </dict>
+               <key>EF1ABB6D22283897006DB8EA</key>
+               <dict>
+                       <key>primary</key>
+                       <true/>
+               </dict>
+               <key>EF1ABB7822283897006DB8EA</key>
+               <dict>
+                       <key>primary</key>
+                       <true/>
+               </dict>
+       </dict>
 </dict>
 </plist>
index dd897ad..0858ee1 100644 (file)
@@ -10,12 +10,12 @@ import Cocoa
 
 @NSApplicationMain
 class AppDelegate: NSObject, NSApplicationDelegate {
-
-    @IBOutlet weak var window: NSWindow!
-
+    var mainWindowController: MainWindowController?
 
     func applicationDidFinishLaunching(_ aNotification: Notification) {
         // Insert code here to initialize your application
+        self.mainWindowController = MainWindowController()
+        self.mainWindowController?.showWindow(self)
     }
 
     func applicationWillTerminate(_ aNotification: Notification) {
index 2db2b1c..6db10bd 100644 (file)
@@ -1,53 +1,63 @@
 {
   "images" : [
     {
-      "idiom" : "mac",
       "size" : "16x16",
+      "idiom" : "mac",
+      "filename" : "orbit16.png",
       "scale" : "1x"
     },
     {
-      "idiom" : "mac",
       "size" : "16x16",
+      "idiom" : "mac",
+      "filename" : "orbit32-1.png",
       "scale" : "2x"
     },
     {
-      "idiom" : "mac",
       "size" : "32x32",
+      "idiom" : "mac",
+      "filename" : "orbit32.png",
       "scale" : "1x"
     },
     {
-      "idiom" : "mac",
       "size" : "32x32",
+      "idiom" : "mac",
+      "filename" : "orbit64.png",
       "scale" : "2x"
     },
     {
-      "idiom" : "mac",
       "size" : "128x128",
+      "idiom" : "mac",
+      "filename" : "orbit128.png",
       "scale" : "1x"
     },
     {
-      "idiom" : "mac",
       "size" : "128x128",
+      "idiom" : "mac",
+      "filename" : "orbit256-1.png",
       "scale" : "2x"
     },
     {
-      "idiom" : "mac",
       "size" : "256x256",
+      "idiom" : "mac",
+      "filename" : "orbit256.png",
       "scale" : "1x"
     },
     {
-      "idiom" : "mac",
       "size" : "256x256",
+      "idiom" : "mac",
+      "filename" : "orbit512-1.png",
       "scale" : "2x"
     },
     {
-      "idiom" : "mac",
       "size" : "512x512",
+      "idiom" : "mac",
+      "filename" : "orbit512.png",
       "scale" : "1x"
     },
     {
-      "idiom" : "mac",
       "size" : "512x512",
+      "idiom" : "mac",
+      "filename" : "orbit1024.png",
       "scale" : "2x"
     }
   ],
diff --git a/OrbitCapture/Assets.xcassets/AppIcon.appiconset/orbit1024.png b/OrbitCapture/Assets.xcassets/AppIcon.appiconset/orbit1024.png
new file mode 100644 (file)
index 0000000..768e856
Binary files /dev/null and b/OrbitCapture/Assets.xcassets/AppIcon.appiconset/orbit1024.png differ
diff --git a/OrbitCapture/Assets.xcassets/AppIcon.appiconset/orbit128.png b/OrbitCapture/Assets.xcassets/AppIcon.appiconset/orbit128.png
new file mode 100644 (file)
index 0000000..e15dcfd
Binary files /dev/null and b/OrbitCapture/Assets.xcassets/AppIcon.appiconset/orbit128.png differ
diff --git a/OrbitCapture/Assets.xcassets/AppIcon.appiconset/orbit16.png b/OrbitCapture/Assets.xcassets/AppIcon.appiconset/orbit16.png
new file mode 100644 (file)
index 0000000..f3eb0a8
Binary files /dev/null and b/OrbitCapture/Assets.xcassets/AppIcon.appiconset/orbit16.png differ
diff --git a/OrbitCapture/Assets.xcassets/AppIcon.appiconset/orbit256-1.png b/OrbitCapture/Assets.xcassets/AppIcon.appiconset/orbit256-1.png
new file mode 100644 (file)
index 0000000..2d52ecf
Binary files /dev/null and b/OrbitCapture/Assets.xcassets/AppIcon.appiconset/orbit256-1.png differ
diff --git a/OrbitCapture/Assets.xcassets/AppIcon.appiconset/orbit256.png b/OrbitCapture/Assets.xcassets/AppIcon.appiconset/orbit256.png
new file mode 100644 (file)
index 0000000..2d52ecf
Binary files /dev/null and b/OrbitCapture/Assets.xcassets/AppIcon.appiconset/orbit256.png differ
diff --git a/OrbitCapture/Assets.xcassets/AppIcon.appiconset/orbit32-1.png b/OrbitCapture/Assets.xcassets/AppIcon.appiconset/orbit32-1.png
new file mode 100644 (file)
index 0000000..fdbb0a9
Binary files /dev/null and b/OrbitCapture/Assets.xcassets/AppIcon.appiconset/orbit32-1.png differ
diff --git a/OrbitCapture/Assets.xcassets/AppIcon.appiconset/orbit32.png b/OrbitCapture/Assets.xcassets/AppIcon.appiconset/orbit32.png
new file mode 100644 (file)
index 0000000..fdbb0a9
Binary files /dev/null and b/OrbitCapture/Assets.xcassets/AppIcon.appiconset/orbit32.png differ
diff --git a/OrbitCapture/Assets.xcassets/AppIcon.appiconset/orbit512-1.png b/OrbitCapture/Assets.xcassets/AppIcon.appiconset/orbit512-1.png
new file mode 100644 (file)
index 0000000..3bb45e0
Binary files /dev/null and b/OrbitCapture/Assets.xcassets/AppIcon.appiconset/orbit512-1.png differ
diff --git a/OrbitCapture/Assets.xcassets/AppIcon.appiconset/orbit512.png b/OrbitCapture/Assets.xcassets/AppIcon.appiconset/orbit512.png
new file mode 100644 (file)
index 0000000..3bb45e0
Binary files /dev/null and b/OrbitCapture/Assets.xcassets/AppIcon.appiconset/orbit512.png differ
diff --git a/OrbitCapture/Assets.xcassets/AppIcon.appiconset/orbit64.png b/OrbitCapture/Assets.xcassets/AppIcon.appiconset/orbit64.png
new file mode 100644 (file)
index 0000000..2b98c81
Binary files /dev/null and b/OrbitCapture/Assets.xcassets/AppIcon.appiconset/orbit64.png differ
index 90c7d86..cc016bf 100644 (file)
@@ -1,21 +1,18 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="11134" systemVersion="15F34" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="13771" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
     <dependencies>
-        <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="11134"/>
+        <deployment identifier="macosx"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="13771"/>
     </dependencies>
     <objects>
         <customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
             <connections>
-                <outlet property="delegate" destination="Voe-Tx-rLC" id="GzC-gU-4Uq"/>
+                <outlet property="delegate" destination="Voe-Tx-rLC" id="vml-Qa-DJT"/>
             </connections>
         </customObject>
         <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
-        <customObject id="-3" userLabel="Application" customClass="NSApplication"/>
-        <customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModuleProvider="target">
-            <connections>
-                <outlet property="window" destination="QvC-M9-y7g" id="gIp-Ho-8D9"/>
-            </connections>
-        </customObject>
+        <customObject id="-3" userLabel="Application" customClass="NSObject"/>
+        <customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="OrbitCapture" customModuleProvider="target"/>
         <customObject id="YLy-65-1bz" customClass="NSFontManager"/>
         <menu title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
             <items>
                 </menuItem>
             </items>
         </menu>
-        <window title="OrbitCapture" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" animationBehavior="default" id="QvC-M9-y7g">
-            <windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
-            <windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
-            <rect key="contentRect" x="335" y="390" width="480" height="360"/>
-            <rect key="screenRect" x="0.0" y="0.0" width="1680" height="1027"/>
-            <view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ">
-                <rect key="frame" x="0.0" y="0.0" width="480" height="360"/>
-                <autoresizingMask key="autoresizingMask"/>
-            </view>
-        </window>
     </objects>
 </document>
diff --git a/OrbitCapture/Base.lproj/MainWindow.xib b/OrbitCapture/Base.lproj/MainWindow.xib
new file mode 100644 (file)
index 0000000..4c144c9
--- /dev/null
@@ -0,0 +1,216 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="13771" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
+    <dependencies>
+        <deployment identifier="macosx"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="13771"/>
+        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+    </dependencies>
+    <objects>
+        <customObject id="-2" userLabel="File's Owner" customClass="MainWindowController" customModule="OrbitCapture" customModuleProvider="target">
+            <connections>
+                <outlet property="captureView" destination="xYb-RH-RKB" id="N1e-LM-x3X"/>
+                <outlet property="connectButton" destination="0GB-Wt-qWq" id="IKe-H6-8yo"/>
+                <outlet property="exposureSlider" destination="zkJ-Oq-jFp" id="i2v-ms-Rg7"/>
+                <outlet property="gainSlider" destination="yjS-UM-9my" id="cZe-sJ-rHS"/>
+                <outlet property="panTiltRange" destination="tGM-kC-rjb" id="9M6-SN-1St"/>
+                <outlet property="whiteBalanceSlider" destination="RKM-cv-nFa" id="sOx-Sp-1Jo"/>
+                <outlet property="window" destination="XRO-bq-me2" id="bMD-me-6Fw"/>
+            </connections>
+        </customObject>
+        <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
+        <customObject id="-3" userLabel="Application" customClass="NSObject"/>
+        <window title="OrbitCapture" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" animationBehavior="default" id="XRO-bq-me2">
+            <windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
+            <windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
+            <rect key="contentRect" x="335" y="390" width="758" height="449"/>
+            <rect key="screenRect" x="0.0" y="0.0" width="2048" height="1129"/>
+            <view key="contentView" wantsLayer="YES" id="uRB-F5-yYK">
+                <rect key="frame" x="0.0" y="0.0" width="758" height="449"/>
+                <autoresizingMask key="autoresizingMask"/>
+                <subviews>
+                    <customView fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="xYb-RH-RKB" customClass="AVCaptureView">
+                        <rect key="frame" x="0.0" y="0.0" width="551" height="449"/>
+                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+                    </customView>
+                    <button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="0GB-Wt-qWq">
+                        <rect key="frame" x="553" y="412" width="191" height="32"/>
+                        <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/>
+                        <buttonCell key="cell" type="push" title="Connect" alternateTitle="Connected" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="zYp-1d-M1c">
+                            <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
+                            <font key="font" metaFont="system"/>
+                        </buttonCell>
+                        <connections>
+                            <action selector="onClickConnectButton:" target="-2" id="s7w-4g-BGv"/>
+                        </connections>
+                    </button>
+                    <button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="qRW-0Y-lnt">
+                        <rect key="frame" x="553" y="314" width="191" height="32"/>
+                        <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/>
+                        <buttonCell key="cell" type="push" title="Tilt+" bezelStyle="rounded" alignment="center" continuous="YES" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="JXF-0M-fto">
+                            <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
+                            <font key="font" metaFont="system"/>
+                        </buttonCell>
+                        <connections>
+                            <action selector="onClickTiltIncButton:" target="-2" id="G9c-fc-ccm"/>
+                            <binding destination="-2" name="enabled" keyPath="connected" id="lkN-b5-XK0"/>
+                        </connections>
+                    </button>
+                    <button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="N6g-Ww-FIW">
+                        <rect key="frame" x="553" y="379" width="191" height="32"/>
+                        <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/>
+                        <buttonCell key="cell" type="push" title="TIlt-" bezelStyle="rounded" alignment="center" continuous="YES" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="Z9g-zp-hxj">
+                            <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
+                            <font key="font" metaFont="system"/>
+                        </buttonCell>
+                        <connections>
+                            <action selector="onClickTiltDecButton:" target="-2" id="v2D-TJ-JLa"/>
+                            <binding destination="-2" name="enabled" keyPath="connected" id="iSl-oS-Ids"/>
+                        </connections>
+                    </button>
+                    <button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="iZo-s3-Pzo">
+                        <rect key="frame" x="663" y="346" width="81" height="32"/>
+                        <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/>
+                        <buttonCell key="cell" type="push" title="Pan+" bezelStyle="rounded" alignment="center" continuous="YES" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="IXI-ZY-12w">
+                            <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
+                            <font key="font" metaFont="system"/>
+                        </buttonCell>
+                        <connections>
+                            <action selector="onClickPanIncButton:" target="-2" id="2Bs-oW-8TB"/>
+                            <binding destination="-2" name="enabled" keyPath="connected" id="8Xq-nx-Um8"/>
+                        </connections>
+                    </button>
+                    <button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="9Sb-ef-SyN">
+                        <rect key="frame" x="553" y="346" width="81" height="32"/>
+                        <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/>
+                        <buttonCell key="cell" type="push" title="Pan-" bezelStyle="rounded" alignment="center" continuous="YES" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="Ptn-PQ-eIk">
+                            <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
+                            <font key="font" metaFont="system"/>
+                        </buttonCell>
+                        <connections>
+                            <action selector="onClickPanDecButton:" target="-2" id="a4N-LT-IEU"/>
+                            <binding destination="-2" name="enabled" keyPath="connected" id="qVf-I9-EXg"/>
+                        </connections>
+                    </button>
+                    <button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="63F-Qc-NlA">
+                        <rect key="frame" x="553" y="260" width="191" height="32"/>
+                        <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/>
+                        <buttonCell key="cell" type="push" title="Reset" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="mCP-1h-Io1">
+                            <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
+                            <font key="font" metaFont="system"/>
+                        </buttonCell>
+                        <connections>
+                            <action selector="onResetPanTiltButton:" target="-2" id="lWT-lY-BUw"/>
+                            <binding destination="-2" name="enabled" keyPath="connected" id="erW-gS-07e"/>
+                        </connections>
+                    </button>
+                    <slider verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="yjS-UM-9my">
+                        <rect key="frame" x="557" y="34" width="183" height="19"/>
+                        <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/>
+                        <sliderCell key="cell" continuous="YES" state="on" alignment="left" maxValue="1" doubleValue="0.5" tickMarkPosition="above" sliderType="linear" id="h4L-uo-N8R"/>
+                        <connections>
+                            <action selector="onUpdateGain:" target="-2" id="pDh-aT-oY6"/>
+                            <binding destination="-2" name="enabled" keyPath="connected" id="Mcv-Lh-kmN"/>
+                        </connections>
+                    </slider>
+                    <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="UYD-3s-uUl">
+                        <rect key="frame" x="557" y="59" width="37" height="17"/>
+                        <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/>
+                        <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Gain:" id="q91-Q0-esZ">
+                            <font key="font" metaFont="system"/>
+                            <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
+                            <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
+                        </textFieldCell>
+                    </textField>
+                    <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="BjZ-Ae-bjc">
+                        <rect key="frame" x="557" y="180" width="106" height="17"/>
+                        <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/>
+                        <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="White Balance:" id="rt1-sM-cEI">
+                            <font key="font" metaFont="system"/>
+                            <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
+                            <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
+                        </textFieldCell>
+                    </textField>
+                    <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="1Cp-wy-c3u">
+                        <rect key="frame" x="557" y="132" width="115" height="17"/>
+                        <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/>
+                        <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Exposure:" id="zn9-5j-SFQ">
+                            <font key="font" metaFont="system"/>
+                            <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
+                            <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
+                        </textFieldCell>
+                    </textField>
+                    <slider verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="zkJ-Oq-jFp">
+                        <rect key="frame" x="557" y="82" width="183" height="19"/>
+                        <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/>
+                        <sliderCell key="cell" continuous="YES" state="on" alignment="left" maxValue="1" doubleValue="0.5" tickMarkPosition="above" sliderType="linear" id="vh0-Oh-fXY"/>
+                        <connections>
+                            <action selector="onUpdateExposureTime:" target="-2" id="Umu-Jd-kHO"/>
+                            <binding destination="-2" name="enabled" keyPath="connected" id="EbS-KG-GM9"/>
+                        </connections>
+                    </slider>
+                    <slider verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="RKM-cv-nFa">
+                        <rect key="frame" x="557" y="155" width="183" height="19"/>
+                        <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/>
+                        <sliderCell key="cell" continuous="YES" state="on" alignment="left" maxValue="1" doubleValue="0.5" tickMarkPosition="above" sliderType="linear" id="GKp-FH-g2Y"/>
+                        <connections>
+                            <action selector="onUpdateWhiteBalance:" target="-2" id="Nkj-6M-jQ1"/>
+                            <binding destination="-2" name="enabled" keyPath="connected" id="Fe5-pa-asp"/>
+                        </connections>
+                    </slider>
+                    <button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="bT0-fz-86d">
+                        <rect key="frame" x="679" y="178" width="61" height="18"/>
+                        <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/>
+                        <buttonCell key="cell" type="check" title="Auto" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="6YY-ND-VdE">
+                            <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
+                            <font key="font" metaFont="system"/>
+                        </buttonCell>
+                        <connections>
+                            <action selector="onUpdateUseAutoWhiteBalance:" target="-2" id="E1x-gK-OwG"/>
+                            <binding destination="-2" name="enabled" keyPath="connected" id="Wkk-AT-4dg"/>
+                        </connections>
+                    </button>
+                    <button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="9Vp-RW-HYN">
+                        <rect key="frame" x="679" y="130" width="61" height="18"/>
+                        <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/>
+                        <buttonCell key="cell" type="check" title="Auto" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="aYz-nK-hv5">
+                            <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
+                            <font key="font" metaFont="system"/>
+                        </buttonCell>
+                        <connections>
+                            <action selector="onUpdateUseAutoExposureTime:" target="-2" id="EaC-7b-J6R"/>
+                            <binding destination="-2" name="enabled" keyPath="connected" id="UJV-L5-LDu"/>
+                        </connections>
+                    </button>
+                    <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="hkK-2q-kCh">
+                        <rect key="frame" x="557" y="107" width="37" height="17"/>
+                        <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/>
+                        <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Time:" id="Ilg-Lc-nqh">
+                            <font key="font" metaFont="system"/>
+                            <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
+                            <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
+                        </textFieldCell>
+                    </textField>
+                    <slider verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="tGM-kC-rjb">
+                        <rect key="frame" x="667" y="294" width="73" height="19"/>
+                        <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/>
+                        <sliderCell key="cell" state="on" alignment="left" minValue="1" maxValue="10" doubleValue="10" tickMarkPosition="above" sliderType="linear" id="sF4-r2-mkY"/>
+                        <connections>
+                            <binding destination="-2" name="enabled" keyPath="connected" id="onY-hd-QjN"/>
+                        </connections>
+                    </slider>
+                    <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="h2m-D0-fxw">
+                        <rect key="frame" x="559" y="296" width="104" height="17"/>
+                        <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/>
+                        <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Pan/Tilt speed:" id="0xE-eg-nVx">
+                            <font key="font" metaFont="system"/>
+                            <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
+                            <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
+                        </textFieldCell>
+                    </textField>
+                </subviews>
+            </view>
+            <point key="canvasLocation" x="270" y="-146.5"/>
+        </window>
+        <userDefaultsController representsSharedInstance="YES" id="VEf-Yr-Tcv"/>
+    </objects>
+</document>
diff --git a/OrbitCapture/LICENSE.txt b/OrbitCapture/LICENSE.txt
new file mode 100644 (file)
index 0000000..81a6a69
--- /dev/null
@@ -0,0 +1,19 @@
+Copyright (c) 2019 Hiromichi Matsushima <hylom@hylom.net>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/OrbitCapture/MainWindowController.swift b/OrbitCapture/MainWindowController.swift
new file mode 100644 (file)
index 0000000..b196859
--- /dev/null
@@ -0,0 +1,199 @@
+//
+//  MainWindowController.swift
+//  OrbitCapture
+//
+//  Created by Hiromichi Matsushima on 2019/03/04.
+//  Copyright © 2019年 Hiromichi Matsushima. All rights reserved.
+//
+
+import Cocoa
+import AVKit
+import AppKit
+
+class MainWindowController: NSWindowController {
+
+    override func windowDidLoad() {
+        super.windowDidLoad()
+        // Implement this method to handle any initialization after your window controller's window has been loaded from its nib file.
+    }
+    
+    override var windowNibName: NSNib.Name? {
+        return NSNib.Name("MainWindow")
+    }
+    @IBOutlet weak var captureView: AVCaptureView!
+    @IBOutlet weak var connectButton: NSButton!
+    @IBOutlet weak var gainSlider: NSSlider!
+    @IBOutlet weak var whiteBalanceSlider: NSSlider!
+    @IBOutlet weak var exposureSlider: NSSlider!
+    @IBOutlet weak var panTiltRange: NSSlider!
+    
+    var cameraControl: UVCCameraControl!
+    @objc dynamic var connected = false
+    
+    override func awakeFromNib() {
+        connected = false
+        captureView.delegate = self
+    }
+    
+    @IBAction func onResetPanTiltButton(_ sender: NSButton) {
+        cameraControl.resetTiltPan(true)
+        print("reset pan and tilt")
+    }
+    
+    @IBAction func onClickPanIncButton(_ sender: NSButton) {
+        if cameraControl != nil {
+            cameraControl.setPanTiltRelative(false, withPan: panTiltRange.intValue, withTilt: 0)
+        }
+    }
+    
+    @IBAction func onClickPanDecButton(_ sender: NSButton) {
+        if cameraControl != nil {
+            cameraControl.setPanTiltRelative(false, withPan: -panTiltRange.intValue, withTilt: 0)
+        }
+    }
+    
+    @IBAction func onClickTiltIncButton(_ sender: NSButton) {
+        if cameraControl != nil {
+            cameraControl.setPanTiltRelative(false, withPan: 0, withTilt: panTiltRange.intValue)
+        }
+    }
+    
+    @IBAction func onClickTiltDecButton(_ sender: NSButton) {
+        if cameraControl != nil {
+            cameraControl.setPanTiltRelative(false, withPan: 0, withTilt: -panTiltRange.intValue)
+        }
+    }
+    
+    @IBAction func onClickConnectButton(_ sender: NSButton) {
+        if let session = captureView.session {
+            let inputs = session.inputs
+            for input in inputs {
+                if let inputDevice = input as? AVCaptureDeviceInput {
+                    let captureDevice = inputDevice.device
+                    print("modelID: \(captureDevice.modelID)")
+                    print("localizedName: \(captureDevice.localizedName)")
+                    print("uniqueID: \(captureDevice.uniqueID)")
+                    print("manufacturer: \(captureDevice.manufacturer)")
+                    /*
+                     modelID: UVC Camera VendorID_1133 ProductID_2452
+                     localizedName: USBカメラ
+                     uniqueID: 0x14340000046d0994
+                     uniqueID: 0x14_34_00_00_04_6d_09_94
+                     */
+                    // get vendor ID and product ID
+                    let rex = try? NSRegularExpression(pattern: "UVC Camera VendorID_(\\d+) ProductID_(\\d+)")
+                    let m = rex!.matches(in: captureDevice.modelID, range:NSRange(location: 0, length:captureDevice.modelID.count))
+                    if (m.count > 0) {
+                        let s = NSString(string: captureDevice.modelID)
+                        let venderId = s.substring(with: m[0].range(at: 1))
+                        let productId = s.substring(with: m[0].range(at: 2))
+
+                        //if captureDevice.modelID == "UVC Camera VendorID_1133 ProductID_2452" {
+                        if venderId == "1133" {
+                            
+                            cameraControl = UVCCameraControl.init(vendorID: Int(venderId)!, productID: Int(productId)!)
+                            if cameraControl != nil {
+                                cameraControl.setAutoExposure(true)
+                                cameraControl.setAutoWhiteBalance(true)
+                                cameraControl.resetTiltPan(true)
+                                cameraControl.setGain(gainSlider.floatValue)
+                                
+                                
+                                if session.canSetSessionPreset(AVCaptureSession.Preset.iFrame1280x720) {
+                                    session.sessionPreset = AVCaptureSession.Preset.iFrame1280x720
+                                }
+                                else if session.canSetSessionPreset(AVCaptureSession.Preset.hd1280x720) {
+                                    session.sessionPreset = AVCaptureSession.Preset.hd1280x720
+                                }
+                                else if session.canSetSessionPreset(AVCaptureSession.Preset.high) {
+                                    session.sessionPreset = AVCaptureSession.Preset.high
+                                }
+                                
+                                connected = true
+                                print("init cameraControl done")
+                                print("use preset \(session.sessionPreset)")
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        if connected {
+            connectButton.isEnabled = false
+        } else {
+            let alert = NSAlert()
+            alert.messageText = "Cannot connect to camera."
+            alert.runModal()
+        }
+    }
+    
+    @IBAction func onUpdateGain(_ sender: NSSlider) {
+        if cameraControl != nil {
+            cameraControl.setGain(gainSlider.floatValue)
+        }
+    }
+    
+    @IBAction func onUpdateWhiteBalance(_ sender: NSSlider) {
+        if cameraControl != nil {
+            // convert log scale
+            cameraControl.setWhiteBalance(whiteBalanceSlider.floatValue)
+        }
+    }
+    
+    @IBAction func onUpdateExposureTime(_ sender: NSSlider) {
+        if cameraControl != nil {
+            let logValue = log10(exposureSlider.floatValue * 9.0 + 1.0)
+            cameraControl.setExposure(logValue)
+        }
+    }
+    
+    @IBAction func onUpdateUseAutoWhiteBalance(_ sender: NSButton) {
+        if cameraControl != nil {
+            if (sender.state == NSControl.StateValue.on) {
+                cameraControl.setAutoWhiteBalance(true)
+            } else {
+                cameraControl.setAutoWhiteBalance(false)
+                cameraControl.setWhiteBalance(whiteBalanceSlider.floatValue)
+            }
+        }
+    }
+    
+    @IBAction func onUpdateUseAutoExposureTime(_ sender: NSButton) {
+        if cameraControl != nil {
+            if (sender.state == NSControl.StateValue.on) {
+                cameraControl.setAutoExposure(true)
+            } else {
+                cameraControl.setAutoExposure(false)
+                let logValue = log10(exposureSlider.floatValue * 9.0 + 1.0)
+                cameraControl.setExposure(logValue)
+                cameraControl.setGain(gainSlider.floatValue)
+            }
+        }
+    }
+
+}
+
+extension MainWindowController: AVCaptureViewDelegate {
+    func captureView(_ captureView: AVCaptureView,
+                     startRecordingTo fileOutput: AVCaptureFileOutput) {
+        let basedir = NSString(string: "~/Desktop").expandingTildeInPath
+        var url = URL(fileURLWithPath: basedir)
+        let movieName = "capture-\(Date()).mov"
+        url.appendPathComponent(movieName, isDirectory: false)
+        
+        print("write movie to \(url)")
+        fileOutput.startRecording(to: url,
+                                  recordingDelegate: self)
+    }
+    
+}
+
+extension MainWindowController: AVCaptureFileOutputRecordingDelegate {
+    func fileOutput(_ output: AVCaptureFileOutput, didFinishRecordingTo outputFileURL: URL, from connections: [AVCaptureConnection], error: Error?) {
+    }
+    
+    func capture(_ captureOutput: AVCaptureFileOutput!,
+                 didFinishRecordingToOutputFileAt outputFileURL: URL!,
+                 fromConnections connections: [Any]!, error: Error!) {
+    }
+}
diff --git a/OrbitCapture/OrbitCapture-Bridging-Header.h b/OrbitCapture/OrbitCapture-Bridging-Header.h
new file mode 100644 (file)
index 0000000..9546816
--- /dev/null
@@ -0,0 +1,5 @@
+//
+//  Use this file to import your target's public headers that you would like to expose to Swift.
+//
+
+#import "UVCCameraControl.h"
index f2ef3ae..0c67376 100644 (file)
@@ -1,10 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
 <plist version="1.0">
-<dict>
-    <key>com.apple.security.app-sandbox</key>
-    <true/>
-    <key>com.apple.security.files.user-selected.read-only</key>
-    <true/>
-</dict>
+<dict/>
 </plist>
diff --git a/OrbitCapture/README.md b/OrbitCapture/README.md
new file mode 100644 (file)
index 0000000..b47592f
--- /dev/null
@@ -0,0 +1,20 @@
+# OrbitCapture
+
+A third-party pan/tilt controller software for Logicool webcam.
+
+You can control pan/tilt, white balance, and exposure.
+
+## Supported platform
+
+* macOS 10.12 or above.
+
+## Tested device
+
+ * Logitech (Logicool) QuickCam Orbit AF
+## How to use
+
+Select webcam from devie list, and click "Connect" button.
+
+## License
+MIT License.
diff --git a/OrbitCapture/UVCCameraControl.h b/OrbitCapture/UVCCameraControl.h
new file mode 100644 (file)
index 0000000..892e1b1
--- /dev/null
@@ -0,0 +1,131 @@
+/*
+ * UVCCameraControl is written by Dominic Szablewski (https://phoboslab.org/log/2009/07/uvc-camera-control-for-mac-os-x),
+ *  kazu (https://github.com/kazu/UVCCameraControl),
+ *  and hylom (https://github.com/hylom).
+ */
+
+#import <Foundation/Foundation.h>
+#import <Cocoa/Cocoa.h>
+
+#include <CoreFoundation/CoreFoundation.h>
+
+#include <IOKit/IOKitLib.h>
+#include <IOKit/IOMessage.h>
+#include <IOKit/IOCFPlugIn.h>
+#include <IOKit/usb/IOUSBLib.h>
+
+
+#define UVC_INPUT_TERMINAL_ID 0x01
+#define UVC_PROCESSING_UNIT_ID 0x02
+
+// for Logitech LXU
+#define UVC_LOGITECH_MOTOR 0x9
+
+
+//TODO: use guid instead of unit id
+//#define UVC_GUID_INPUT_TERMINAL_ID {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}
+//#define UVC_GUID_PROCESSING_UNIT_ID {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02}
+//#define UVC_GUID_LOGITECH_MOTOR {0x82, 0x06, 0x61, 0x63, 0x70, 0x50, 0xab, 0x49, \
+0xb8, 0xcc, 0xb3, 0x85, 0x5e, 0x8d, 0x22, 0x56}
+
+
+#define LXU_MOTOR_PANTILT_RELATIVE_CONTROL              0x01
+#define LXU_MOTOR_PANTILT_RESET_CONTROL                 0x02
+#define LXU_MOTOR_FOCUS_MOTOR_CONTROL                   0x03
+
+#define XU_HW_CONTROL_LED1               1
+#define XU_MOTORCONTROL_PANTILT_RELATIVE 1
+#define XU_MOTORCONTROL_PANTILT_RESET    2
+#define XU_MOTORCONTROL_FOCUS            3
+
+#define UVC_CONTROL_INTERFACE_CLASS 14
+#define UVC_CONTROL_INTERFACE_SUBCLASS 1
+       
+#define UVC_SET_CUR    0x01
+#define UVC_GET_CUR    0x81
+#define UVC_GET_MIN    0x82
+#define UVC_GET_MAX    0x83
+
+#define UVC_GET_RES 0x84
+#define UVC_GET_LEN 0x85
+#define UVC_GET_INFO 0x86
+#define UVC_GET_DEF 0x87
+
+
+typedef struct {
+       int min, max;
+} uvc_range_t;
+
+typedef struct {
+       int unit;
+       int selector;
+       int size;
+} uvc_control_info_t;
+
+typedef struct {
+       uvc_control_info_t autoExposure;
+       uvc_control_info_t exposure;
+    uvc_control_info_t zoomrel;
+    uvc_control_info_t zoom;
+       uvc_control_info_t autoFocus;
+       uvc_control_info_t pantiltrel;
+       uvc_control_info_t pantilt_reset;
+       uvc_control_info_t brightness;
+       uvc_control_info_t contrast;
+       uvc_control_info_t gain;
+       uvc_control_info_t saturation;
+       uvc_control_info_t sharpness;
+       uvc_control_info_t whiteBalance;
+       uvc_control_info_t autoWhiteBalance;
+} uvc_controls_t ;
+
+
+@interface UVCCameraControl : NSObject {
+       long dataBuffer;
+       IOUSBInterfaceInterface190 **interface;
+}
+
+
+- (id)initWithLocationID:(UInt32)locationID;
+- (id)initWithVendorID:(long)vendorID productID:(long)productID;
+- (IOUSBInterfaceInterface190 **)getControlInferaceWithDeviceInterface:(IOUSBDeviceInterface **)deviceInterface;
+
+- (BOOL)sendControlRequest:(IOUSBDevRequest)controlRequest;
+- (BOOL)setData:(long)value withLength:(int)length forSelector:(int)selector at:(int)unitID;
+- (BOOL)setData2:(void *)value withLength:(int)length forSelector:(int)selector at:(int)unitID;
+
+- (long)getDataFor:(int)type withLength:(int)length fromSelector:(int)selector at:(int)unitID;
+
+- (uvc_range_t)getRangeForControl:(const uvc_control_info_t *)control;
+- (float)mapValue:(float)value fromMin:(float)fromMin max:(float)fromMax toMin:(float)toMin max:(float)toMax;
+- (float)getValueForControl:(const uvc_control_info_t *)control;
+- (BOOL)setValue:(float)value forControl:(const uvc_control_info_t *)control;
+
+- (BOOL)setAutoExposure:(BOOL)enabled;
+- (BOOL)setPanTiltRelative:(BOOL)reset withPan:(int)pan withTilt:(int)tilt;
+- (BOOL)setZoomRelative:(int)zoom_rel withSpeed:(int)speed;
+- (BOOL)setZoom:(int)value;
+- (BOOL)resetTiltPan:(BOOL)enabled;
+- (BOOL)getAutoExposure;
+- (BOOL)setExposure:(float)value;
+- (float)getZoom;
+- (float)getExposure;
+- (BOOL)setGain:(float)value;
+- (float)getGain;
+- (BOOL)setBrightness:(float)value;
+- (float)getBrightness;
+- (BOOL)setContrast:(float)value;
+- (float)getContrast;
+- (BOOL)setSaturation:(float)value;
+- (float)getSaturation;
+- (BOOL)setSharpness:(float)value;
+- (float)getSharpness;
+- (BOOL)setAutoWhiteBalance:(BOOL)enabled;
+- (BOOL)getAutoWhiteBalance;
+- (BOOL)setWhiteBalance:(float)value;
+- (float)getWhiteBalance;
+- (BOOL)listOfUVCdevice:(UInt32)deviceclass;
+
+@end
diff --git a/OrbitCapture/UVCCameraControl.m b/OrbitCapture/UVCCameraControl.m
new file mode 100644 (file)
index 0000000..49c4e3a
--- /dev/null
@@ -0,0 +1,636 @@
+/*
+ * UVCCameraControl is written by Dominic Szablewski (https://phoboslab.org/log/2009/07/uvc-camera-control-for-mac-os-x),
+ *  kazu (https://github.com/kazu/UVCCameraControl),
+ *  and hylom (https://github.com/hylom).
+ */
+
+
+#import "UVCCameraControl.h"
+
+
+const uvc_controls_t uvc_controls = {
+       .autoExposure = {
+               .unit = UVC_INPUT_TERMINAL_ID,
+               .selector = 0x02,
+               .size = 1,
+       },
+       .exposure = {
+               .unit = UVC_INPUT_TERMINAL_ID,
+               .selector = 0x04,
+               .size = 4,
+       },
+    .zoom = {
+        .unit = UVC_INPUT_TERMINAL_ID,
+        .selector = 0x0b,
+        .size = 2,
+    },
+    .zoomrel = {
+        .unit = UVC_INPUT_TERMINAL_ID,
+        .selector = 0x0c,
+        .size = 3,
+    },
+       .autoFocus = {
+               .unit = UVC_INPUT_TERMINAL_ID,
+               .selector = 0x08,
+               .size = 1,
+       },
+       .pantiltrel = {
+               .unit = UVC_LOGITECH_MOTOR,
+               .selector = LXU_MOTOR_PANTILT_RELATIVE_CONTROL,
+               .size = 4,
+       },
+       .pantilt_reset = {
+               .unit = UVC_LOGITECH_MOTOR,
+               .selector = LXU_MOTOR_PANTILT_RESET_CONTROL,
+               .size = 1,
+       },
+       .brightness = {
+               .unit = UVC_PROCESSING_UNIT_ID,
+               .selector = 0x02,
+               .size = 2,
+       },
+       .contrast = {
+               .unit = UVC_PROCESSING_UNIT_ID,
+               .selector = 0x03,
+               .size = 2,
+       },
+       .gain = {
+               .unit = UVC_PROCESSING_UNIT_ID,
+               .selector = 0x04,
+               .size = 2,
+       },
+       .saturation = {
+               .unit = UVC_PROCESSING_UNIT_ID,
+               .selector = 0x07,
+               .size = 2,
+       },
+       .sharpness = {
+               .unit = UVC_PROCESSING_UNIT_ID,
+               .selector = 0x08,
+               .size = 2,
+       },
+       .whiteBalance = {
+               .unit = UVC_PROCESSING_UNIT_ID,
+               .selector = 0x0A,
+               .size = 2,
+       },
+       .autoWhiteBalance = {
+               .unit = UVC_PROCESSING_UNIT_ID,
+               .selector = 0x0B,
+               .size = 1,
+       },
+};
+
+
+@implementation UVCCameraControl
+
+
+- (BOOL)listOfUVCdevice:(UInt32)deviceclass {
+       // Find All USB Devices, get their locationId and check if it matches the requested one
+       CFMutableDictionaryRef matchingDict = IOServiceMatching(kIOUSBDeviceClassName);
+       io_iterator_t serviceIterator;
+       IOServiceGetMatchingServices( kIOMasterPortDefault, matchingDict, &serviceIterator );
+       
+       io_service_t camera;
+       while( camera = IOIteratorNext(serviceIterator) ) {
+               // Get DeviceInterface
+               IOUSBDeviceInterface **deviceInterface = NULL;
+               IOCFPlugInInterface     **plugInInterface = NULL;
+               SInt32 score;
+               kern_return_t kr = IOCreatePlugInInterfaceForService( camera, kIOUSBDeviceUserClientTypeID, kIOCFPlugInInterfaceID, &plugInInterface, &score );
+               if( (kIOReturnSuccess != kr) || !plugInInterface ) {
+                       NSLog( @"CameraControl Error: IOCreatePlugInInterfaceForService returned 0x%08x.", kr );
+                       continue;
+               }
+               
+               HRESULT res = (*plugInInterface)->QueryInterface(plugInInterface, CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID), (LPVOID*) &deviceInterface );
+               (*plugInInterface)->Release(plugInInterface);
+               if( res || deviceInterface == NULL ) {
+                       NSLog( @"CameraControl Error: QueryInterface returned %d.\n", (int)res );
+                       continue;
+               }
+               UInt8 currentDeviceClass;
+               (*deviceInterface)->GetDeviceClass(deviceInterface, &currentDeviceClass);
+               if(currentDeviceClass != kUSBMiscellaneousClass){
+                       //printf("class 0x%x UVC 0x%x \n",currentDeviceClass,kUSBMiscellaneousClass);
+                       continue;
+               }
+               
+               UInt32 currentLocationID = 0;
+               (*deviceInterface)->GetLocationID(deviceInterface, &currentLocationID);
+               
+               UInt16 currentVendor = 0;
+               (*deviceInterface)->GetDeviceVendor(deviceInterface, &currentVendor);
+               
+               UInt16 currentProduct = 0;
+               (*deviceInterface)->GetDeviceProduct(deviceInterface, &currentProduct);
+               
+               
+               printf("LocationID: 0x%x VendorID: 0x%x ProductID: 0x%x\n",
+              currentLocationID,
+              currentVendor,
+              currentProduct);
+               
+       } // end while
+       return TRUE;
+
+}
+- (id)initWithLocationID:(UInt32)locationID {
+       if( self = [super init] ) {
+               interface = NULL;
+               
+               // Find All USB Devices, get their locationId and check if it matches the requested one
+               CFMutableDictionaryRef matchingDict = IOServiceMatching(kIOUSBDeviceClassName);
+               io_iterator_t serviceIterator;
+               IOServiceGetMatchingServices( kIOMasterPortDefault, matchingDict, &serviceIterator );
+               
+               io_service_t camera;
+               while( camera = IOIteratorNext(serviceIterator) ) {
+                       // Get DeviceInterface
+                       IOUSBDeviceInterface **deviceInterface = NULL;
+                       IOCFPlugInInterface     **plugInInterface = NULL;
+                       SInt32 score;
+                       kern_return_t kr = IOCreatePlugInInterfaceForService( camera, kIOUSBDeviceUserClientTypeID, kIOCFPlugInInterfaceID, &plugInInterface, &score );
+                       if( (kIOReturnSuccess != kr) || !plugInInterface ) {
+                               NSLog( @"CameraControl Error: IOCreatePlugInInterfaceForService returned 0x%08x.", kr );
+                               continue;
+                       }
+                       
+                       HRESULT res = (*plugInInterface)->QueryInterface(plugInInterface, CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID), (LPVOID*) &deviceInterface );
+                       (*plugInInterface)->Release(plugInInterface);
+                       if( res || deviceInterface == NULL ) {
+                               NSLog( @"CameraControl Error: QueryInterface returned %d.\n", (int)res );
+                               continue;
+                       }
+                       
+                       UInt32 currentLocationID = 0;
+                       (*deviceInterface)->GetLocationID(deviceInterface, &currentLocationID);
+                
+                       UInt16 currentVendor = 0;
+                       (*deviceInterface)->GetDeviceVendor(deviceInterface, &currentVendor);
+
+                       UInt16 currentProduct = 0;
+                       (*deviceInterface)->GetDeviceProduct(deviceInterface, &currentProduct);
+
+/*
+                       NSLog(@"LocationID: 0x%x VendorID: 0x%x ProductID: 0x%x",
+              currentLocationID,
+              currentVendor,
+              currentProduct);
+*/
+                       if( currentLocationID == locationID ) {
+                               // Yep, this is the USB Device that was requested!
+                               interface = [self getControlInferaceWithDeviceInterface:deviceInterface];
+                               return self;
+                       }
+               } // end while
+               
+       }
+       return self;
+}
+
+
+- (id)initWithVendorID:(long)vendorID productID:(long)productID {
+       if( self = [super init] ) {
+               interface = NULL;
+               
+               // Find USB Device
+               CFMutableDictionaryRef matchingDict = IOServiceMatching(kIOUSBDeviceClassName);
+               CFNumberRef numberRef;
+               
+               numberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &vendorID);
+               CFDictionarySetValue( matchingDict, CFSTR(kUSBVendorID), numberRef );
+               CFRelease(numberRef);
+               
+               numberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &productID);
+               CFDictionarySetValue( matchingDict, CFSTR(kUSBProductID), numberRef );
+               CFRelease(numberRef);
+               io_service_t camera = IOServiceGetMatchingService( kIOMasterPortDefault, matchingDict );
+               
+               
+               // Get DeviceInterface
+               IOUSBDeviceInterface **deviceInterface = NULL;
+               IOCFPlugInInterface     **plugInInterface = NULL;
+               SInt32 score;
+               kern_return_t kr = IOCreatePlugInInterfaceForService( camera, kIOUSBDeviceUserClientTypeID, kIOCFPlugInInterfaceID, &plugInInterface, &score );
+        if( (kIOReturnSuccess != kr) || !plugInInterface ) {
+            NSLog( @"CameraControl Error: IOCreatePlugInInterfaceForService returned 0x%08x.", kr );
+                       return self;
+        }
+               
+        HRESULT res = (*plugInInterface)->QueryInterface(plugInInterface, CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID), (LPVOID*) &deviceInterface );
+        (*plugInInterface)->Release(plugInInterface);
+        if( res || deviceInterface == NULL ) {
+            NSLog( @"CameraControl Error: QueryInterface returned %d.\n", (int)res );
+            return self;
+        }
+               
+               interface = [self getControlInferaceWithDeviceInterface:deviceInterface];
+       }
+       return self;
+}
+
+
+- (IOUSBInterfaceInterface190 **)getControlInferaceWithDeviceInterface:(IOUSBDeviceInterface **)deviceInterface {
+       IOUSBInterfaceInterface190 **controlInterface;
+       
+       io_iterator_t interfaceIterator;
+       IOUSBFindInterfaceRequest interfaceRequest;
+       interfaceRequest.bInterfaceClass = UVC_CONTROL_INTERFACE_CLASS;
+       interfaceRequest.bInterfaceSubClass = UVC_CONTROL_INTERFACE_SUBCLASS;
+       interfaceRequest.bInterfaceProtocol = kIOUSBFindInterfaceDontCare;
+       interfaceRequest.bAlternateSetting = kIOUSBFindInterfaceDontCare;
+       
+       IOReturn success = (*deviceInterface)->CreateInterfaceIterator( deviceInterface, &interfaceRequest, &interfaceIterator );
+       if( success != kIOReturnSuccess ) {
+               return NULL;
+       }
+       
+       io_service_t usbInterface;
+       HRESULT result;
+       
+       
+       if( usbInterface = IOIteratorNext(interfaceIterator) ) {
+               IOCFPlugInInterface **plugInInterface = NULL;
+               
+               //Create an intermediate plug-in
+               SInt32 score;
+               kern_return_t kr = IOCreatePlugInInterfaceForService( usbInterface, kIOUSBInterfaceUserClientTypeID, kIOCFPlugInInterfaceID, &plugInInterface, &score );
+               
+               //Release the usbInterface object after getting the plug-in
+               kr = IOObjectRelease(usbInterface);
+               if( (kr != kIOReturnSuccess) || !plugInInterface ) {
+                       NSLog( @"CameraControl Error: Unable to create a plug-in (%08x)\n", kr );
+                       return NULL;
+               }
+               
+               //Now create the device interface for the interface
+               result = (*plugInInterface)->QueryInterface( plugInInterface, CFUUIDGetUUIDBytes(kIOUSBInterfaceInterfaceID), (LPVOID *) &controlInterface );
+               
+               //No longer need the intermediate plug-in
+               (*plugInInterface)->Release(plugInInterface);
+               
+               if( result || !controlInterface ) {
+                       NSLog( @"CameraControl Error: Couldn’t create a device interface for the interface (%08x)", (int) result );
+                       return NULL;
+               }
+               
+               return controlInterface;
+       }
+       
+       return NULL;
+}
+
+
+- (void)dealloc {
+       if( interface ) {
+               (*interface)->USBInterfaceClose(interface);
+               (*interface)->Release(interface);
+       }
+       //[super dealloc];
+}
+
+
+- (BOOL)sendControlRequest:(IOUSBDevRequest)controlRequest {
+       if( !interface ){
+               NSLog( @"CameraControl Error: No interface to send request" );
+               return NO;
+       }
+       
+       //Now open the interface. This will cause the pipes associated with
+       //the endpoints in the interface descriptor to be instantiated
+       kern_return_t kr = (*interface)->USBInterfaceOpen(interface);
+       if (kr != kIOReturnSuccess)     {
+               NSLog( @"CameraControl Error: Unable to open interface (%08x)\n", kr );
+               return NO;
+       }
+       
+       kr = (*interface)->ControlRequest( interface, 0, &controlRequest );
+       if( kr != kIOReturnSuccess ) {
+               kr = (*interface)->USBInterfaceClose(interface);
+               NSLog( @"CameraControl Error: Control request failed: %08x", kr );
+               return NO;
+       }
+       
+       kr = (*interface)->USBInterfaceClose(interface);
+       
+       return YES;
+}
+
+
+- (BOOL)setData:(long)value withLength:(int)length forSelector:(int)selector at:(int)unitId {
+       IOUSBDevRequest controlRequest;
+#ifdef DEBUG_UVC_CAMERA
+       NSLog(@"setData unitid %d",unitId);
+       NSLog(@"setData selector %d",selector);
+       NSLog(@"setData length %d",length);
+#endif
+       controlRequest.bmRequestType = USBmakebmRequestType( kUSBOut, kUSBClass, kUSBInterface );
+       controlRequest.bRequest = UVC_SET_CUR;
+       controlRequest.wValue = (selector << 8) | 0x00;
+       controlRequest.wIndex = (unitId << 8) | 0x00;
+       controlRequest.wLength = length;
+       controlRequest.wLenDone = 0;
+       controlRequest.pData = &value;
+       return [self sendControlRequest:controlRequest];
+}
+
+/* 
+  support pointer value of setData
+ */
+
+- (BOOL)setData2:(void *)value withLength:(int)length forSelector:(int)selector at:(int)unitId
+{
+       IOUSBDevRequest controlRequest;
+#ifdef DEBUG_UVC_CAMERA
+       NSLog(@"setData2 unitid %d",unitId);
+       NSLog(@"setData2 selector %d",selector);
+       NSLog(@"setData2 length %d",length);
+#endif
+       controlRequest.bmRequestType = USBmakebmRequestType( kUSBOut, kUSBClass, kUSBInterface );
+       controlRequest.bRequest = UVC_SET_CUR;
+       controlRequest.wValue = ( selector << 8) | 0x00;
+       controlRequest.wIndex = ( unitId <<8 ) | 0x00; 
+       controlRequest.wLength = length;
+       controlRequest.wLenDone = 0;
+       controlRequest.pData = value;
+       return [self sendControlRequest:controlRequest];
+}
+
+- (long)getDataFor:(int)type withLength:(int)length fromSelector:(int)selector at:(int)unitId {
+       long value = 0;
+       IOUSBDevRequest controlRequest;
+       controlRequest.bmRequestType = USBmakebmRequestType( kUSBIn, kUSBClass, kUSBInterface );
+       controlRequest.bRequest = type;
+       controlRequest.wValue = (selector << 8) | 0x00;
+       controlRequest.wIndex = (unitId << 8) | 0x00;
+       controlRequest.wLength = length;
+       controlRequest.wLenDone = 0;
+       controlRequest.pData = &value;
+       BOOL success = [self sendControlRequest:controlRequest];
+       return ( success ? value : 0 );
+}
+
+
+// Get Range (min, max)
+- (uvc_range_t)getRangeForControl:(uvc_control_info_t *)control {
+       uvc_range_t range = { 0, 0 };
+       range.min = [self getDataFor:UVC_GET_MIN withLength:control->size fromSelector:control->selector at:control->unit];
+       range.max = [self getDataFor:UVC_GET_MAX withLength:control->size fromSelector:control->selector at:control->unit];
+#ifdef DEBUG_UVC_CAMERA
+       NSLog(@"getRangeForControl min:%d max:%d",range.min,range.max);
+#endif
+       return range;
+}
+
+
+// Used to de-/normalize values
+- (float)mapValue:(float)value fromMin:(float)fromMin max:(float)fromMax toMin:(float)toMin max:(float)toMax {
+       return toMin + (toMax - toMin) * ((value - fromMin) / (fromMax - fromMin));
+}
+
+
+// Get a normalized value
+- (float)getValueForControl:(uvc_control_info_t *)control {
+       // TODO: Cache the range somewhere?
+       uvc_range_t range = [self getRangeForControl:control];
+       
+       int intval = [self getDataFor:UVC_GET_CUR withLength:control->size fromSelector:control->selector at:control->unit];
+       return [self mapValue:intval fromMin:range.min max:range.max toMin:0 max:1];
+}
+
+
+// Set a normalized value
+- (BOOL)setValue:(float)value forControl:(uvc_control_info_t *)control {
+       // TODO: Cache the range somewhere?
+       uvc_range_t range = [self getRangeForControl:control];
+       
+       int intval = [self mapValue:value fromMin:0 max:1 toMin:range.min max:range.max];
+       return [self setData:intval withLength:control->size forSelector:control->selector at:control->unit];
+}
+
+
+
+
+
+// ================================================================
+
+// Set/Get the actual values for the camera
+//
+
+- (BOOL)setAutoExposure:(BOOL)enabled {
+       int intval = (enabled ? 0x08 : 0x01); // "auto exposure modes" ar NOT boolean (on|off) as it seems
+       return [self setData:intval 
+                         withLength:uvc_controls.autoExposure.size 
+                        forSelector:uvc_controls.autoExposure.selector 
+                                         at:uvc_controls.autoExposure.unit];
+}
+
+- (BOOL)getAutoExposure {
+       int intval = [self getDataFor:UVC_GET_CUR 
+                                          withLength:uvc_controls.autoExposure.size 
+                                        fromSelector:uvc_controls.autoExposure.selector 
+                                                          at:uvc_controls.autoExposure.unit];
+       
+       return ( intval == 0x08 ? YES : NO );
+}
+// TODO: support AutoFocus
+//   Logitech Orbit dont support AutoFocus
+
+- (BOOL)setAutoFocus:(BOOL)enabled {
+       int intval = (enabled ? 0x01 : 0x00); // "auto exposure modes" ar NOT boolean (on|off) as it seems
+       int retval = [self getDataFor:UVC_GET_CUR 
+                                          withLength:uvc_controls.autoFocus.size 
+                                        fromSelector:uvc_controls.autoFocus.selector 
+                                                          at:uvc_controls.autoFocus.unit];
+#ifdef DEBUG_UVC_CAMERA
+       NSLog(@" getFocus %d ",retval);
+    //test resetPanTilt
+    int i,ret;
+       //for(i=5;i<100;i++){
+               ret = [self setData:1
+                       withLength:1
+                  forSelector:2
+                                       at:9];
+               NSLog(@"%d: %d",9,ret);
+               //sleep(5);
+       //}
+#endif 
+       return [self setData:intval 
+                         withLength:uvc_controls.autoFocus.size 
+                        forSelector:uvc_controls.autoFocus.selector 
+                                         at:uvc_controls.autoFocus.unit];
+}
+
+- (BOOL)setExposure:(float)value {
+       value = 1 - value;
+       return [self setValue:value forControl:&uvc_controls.exposure];
+}
+
+- (float)getExposure {
+       float value = [self getValueForControl:&uvc_controls.exposure];
+       return 1 - value;
+}
+
+- (BOOL)setGain:(float)value {
+       return [self setValue:value forControl:&uvc_controls.gain];
+}
+
+- (float)getGain {
+       return [self getValueForControl:&uvc_controls.gain];
+}
+
+- (BOOL)setBrightness:(float)value {
+       return [self setValue:value forControl:&uvc_controls.brightness];
+}
+
+- (float)getBrightness {
+       return [self getValueForControl:&uvc_controls.brightness];
+}
+
+- (BOOL)setContrast:(float)value {
+       return [self setValue:value forControl:&uvc_controls.contrast];
+}
+
+- (float)getContrast {
+       return [self getValueForControl:&uvc_controls.contrast];
+}
+
+- (BOOL)setSaturation:(float)value {
+       return [self setValue:value forControl:&uvc_controls.saturation];
+}
+
+- (float)getSaturation {
+       return [self getValueForControl:&uvc_controls.saturation];
+}
+
+- (BOOL)setSharpness:(float)value {
+       return [self setValue:value forControl:&uvc_controls.sharpness];
+}
+
+- (float)getSharpness {
+       return [self getValueForControl:&uvc_controls.sharpness];
+}
+
+- (BOOL)setAutoWhiteBalance:(BOOL)enabled {
+       int intval = (enabled ? 0x01 : 0x00);
+       return [self setData:intval 
+                         withLength:uvc_controls.autoWhiteBalance.size 
+                        forSelector:uvc_controls.autoWhiteBalance.selector 
+                                         at:uvc_controls.autoWhiteBalance.unit];
+}
+// resetTiltPan support only logitech Orbit.
+
+- (BOOL)resetTiltPan:(BOOL)enabled {
+  int8_t val = 3;
+       return [self setData2:&val
+                        withLength:uvc_controls.pantilt_reset.size
+                       forSelector:uvc_controls.pantilt_reset.selector 
+                                        at:uvc_controls.pantilt_reset.unit];
+ }
+
+// resetTiltPan support only logitech Orbit.
+- (BOOL)setPanTiltRelative:(BOOL)reset withPan:(int)pan withTilt:(int)tilt {
+    int16_t value[2];
+       BOOL ret;
+       
+    value[0] = pan * 64;
+       value[1] = tilt * 64;
+       
+#ifdef DEBUG_UVC_CAMERA        
+       NSLog(@"setPanTilt pan %d tilt %d",value[0],value[1]);
+#endif 
+       
+       ret = [self setData2:(void *)value
+                         withLength:uvc_controls.pantiltrel.size
+                        forSelector:uvc_controls.pantiltrel.selector 
+                                         at:uvc_controls.pantiltrel.unit];
+
+#ifdef DEBUG_UVC_CAMERA
+       if(ret) {
+               NSLog(@"setPanTilt: success");
+       } else {
+               NSLog(@"setPanTilt: fail");
+       }
+#endif
+
+    return ret;
+}
+
+- (BOOL)setZoom:(int)value {
+    int16_t buf[1];
+    BOOL ret;
+    
+    uvc_control_info_t* control = &uvc_controls.zoomrel;
+    int defined = [self getDataFor:UVC_GET_DEF withLength:control->size fromSelector:control->selector at:control->unit];
+    if (!defined) {
+        return false;
+    }
+
+    buf[0] = value;
+    ret = [self setData2:(void *)buf
+              withLength:uvc_controls.zoomrel.size
+             forSelector:uvc_controls.zoomrel.selector
+                      at:uvc_controls.zoomrel.unit];
+    
+    //#ifdef DEBUG_UVC_CAMERA
+    if(ret) {
+        NSLog(@"setZoomRelative: success");
+    } else {
+        NSLog(@"setZoomRelative: fail");
+    }
+    //#endif
+    
+    return ret;
+    //return [self setValue:value forControl:&uvc_controls.zoom];
+}
+
+- (BOOL)setZoomRelative:(int)zoom_rel withSpeed:(int)speed {
+    int16_t value[3];
+    BOOL ret;
+    
+    value[0] = zoom_rel;
+    value[1] = 1;  // digital_zoom
+    value[2] = speed;
+    
+    //#ifdef DEBUG_UVC_CAMERA
+    NSLog(@"setZoomRelative zoom_rel %d speed %d",value[0],value[2]);
+    //#endif
+    
+    ret = [self setData2:(void *)value
+              withLength:uvc_controls.zoomrel.size
+             forSelector:uvc_controls.zoomrel.selector
+                      at:uvc_controls.zoomrel.unit];
+    
+    //#ifdef DEBUG_UVC_CAMERA
+    if(ret) {
+        NSLog(@"setZoomRelative: success");
+    } else {
+        NSLog(@"setZoomRelative: fail");
+    }
+    //#endif
+    
+    return ret;
+}
+
+- (BOOL)getAutoWhiteBalance {
+       int intval = [self getDataFor:UVC_GET_CUR 
+                                          withLength:uvc_controls.autoWhiteBalance.size 
+                                        fromSelector:uvc_controls.autoWhiteBalance.selector 
+                                                          at:uvc_controls.autoWhiteBalance.unit];
+       
+       return ( intval ? YES : NO );
+}
+
+- (BOOL)setWhiteBalance:(float)value {
+       return [self setValue:value forControl:&uvc_controls.whiteBalance];
+}
+
+- (float)getWhiteBalance {
+       return [self getValueForControl:&uvc_controls.whiteBalance];
+}
+
+
+@end
+
+void Init_uvccameracontrol(){}
diff --git a/orbitcam.psd b/orbitcam.psd
new file mode 100644 (file)
index 0000000..29885d3
Binary files /dev/null and b/orbitcam.psd differ
diff --git a/orbitcam_small.psd b/orbitcam_small.psd
new file mode 100644 (file)
index 0000000..f9b2ac9
Binary files /dev/null and b/orbitcam_small.psd differ