diff --git a/.github/workflows/build-royalvncdemo.yml b/.github/workflows/build-royalvncdemo.yml new file mode 100644 index 0000000..2e76585 --- /dev/null +++ b/.github/workflows/build-royalvncdemo.yml @@ -0,0 +1,27 @@ +name: Build RoyalVNC Demo + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + build: + name: Build + runs-on: macos-14 + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Select Xcode + run: sudo xcode-select --switch /Applications/Xcode_16.0.app + + - name: Get Xcode Version + run: xcodebuild -version + + - name: Build + env: + scheme: RoyalVNCDemo + run: xcodebuild clean build analyze -scheme "$scheme" | xcpretty && exit ${PIPESTATUS[0]} diff --git a/.github/workflows/build-royalvnciosdemo.yml b/.github/workflows/build-royalvnciosdemo.yml new file mode 100644 index 0000000..541d772 --- /dev/null +++ b/.github/workflows/build-royalvnciosdemo.yml @@ -0,0 +1,28 @@ +name: Build RoyalVNC iOS Demo + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + build: + name: Build + runs-on: macos-14 + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Select Xcode + run: sudo xcode-select --switch /Applications/Xcode_16.0.app + + - name: Get Xcode Version + run: xcodebuild -version + + - name: Build + env: + scheme: RoyalVNCiOSDemo + destination: generic/platform=iOS Simulator + run: xcodebuild clean build analyze -scheme "$scheme" -destination "$destination" | xcpretty && exit ${PIPESTATUS[0]} diff --git a/.github/workflows/build-royalvncobjcdemo.yml b/.github/workflows/build-royalvncobjcdemo.yml new file mode 100644 index 0000000..1e393fe --- /dev/null +++ b/.github/workflows/build-royalvncobjcdemo.yml @@ -0,0 +1,27 @@ +name: Build RoyalVNC ObjC Demo + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + build: + name: Build + runs-on: macos-14 + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Select Xcode + run: sudo xcode-select --switch /Applications/Xcode_16.0.app + + - name: Get Xcode Version + run: xcodebuild -version + + - name: Build + env: + scheme: RoyalVNCObjCDemo + run: xcodebuild clean build analyze -scheme "$scheme" | xcpretty && exit ${PIPESTATUS[0]} diff --git a/.swiftlint.yml b/.swiftlint.yml new file mode 100644 index 0000000..cd59e7d --- /dev/null +++ b/.swiftlint.yml @@ -0,0 +1,51 @@ +disabled_rules: # rule identifiers turned on by default to exclude from running + - trailing_whitespace + - vertical_parameter_alignment + - switch_case_alignment + - todo + +opt_in_rules: + - unavailable_function + - closure_spacing + - discarded_notification_center_observer + - discouraged_assert + - empty_count + - empty_string + - enum_case_associated_values_count + - fatal_error_message + - file_name_no_space + - function_default_parameter_at_end + - ibinspectable_in_extension + - identical_operands + - legacy_multiple + - literal_expression_end_indentation + - lower_acl_than_parent + - override_in_extension + - prefer_self_type_over_type_of_self + - prefer_zero_over_explicit_init + - private_action + - private_outlet + - redundant_nil_coalescing + - redundant_type_annotation + - toggle_bool + - unneeded_parentheses_in_closure_argument + - unowned_variable_capture + - weak_delegate + - yoda_condition + +analyzer_rules: + - unused_declaration + - unused_import + +file_length: 800 +line_length: 280 +function_body_length: 82 +function_parameter_count: 8 +cyclomatic_complexity: 16 + +identifier_name: + min_length: 2 + max_length: 60 + allowed_symbols: [ + _ + ] diff --git a/README.md b/README.md index ab48178..aeb7bf4 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,3 @@ -# royalvnc-demo -Demo clients for RoyalVNC +# Royal VNC Demos + +Demo clients for [RoyalVNC](https://github.com/royalapplications/royalvnc) diff --git a/RoyalVNC.xcworkspace/contents.xcworkspacedata b/RoyalVNC.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..7106527 --- /dev/null +++ b/RoyalVNC.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + diff --git a/RoyalVNC.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/RoyalVNC.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/RoyalVNC.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/RoyalVNCDemo/RoyalVNCDemo.xcodeproj/project.pbxproj b/RoyalVNCDemo/RoyalVNCDemo.xcodeproj/project.pbxproj new file mode 100644 index 0000000..a1c8bde --- /dev/null +++ b/RoyalVNCDemo/RoyalVNCDemo.xcodeproj/project.pbxproj @@ -0,0 +1,474 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 55; + objects = { + +/* Begin PBXBuildFile section */ + E742BD1F29018500007D23D5 /* ConfigurationWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = E742BD1E29018500007D23D5 /* ConfigurationWindow.xib */; }; + E742BD2129018513007D23D5 /* ConfigurationWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E742BD2029018513007D23D5 /* ConfigurationWindowController.swift */; }; + E761FEA728C23AEF005EFA94 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E761FEA628C23AEF005EFA94 /* AppDelegate.swift */; }; + E761FEA928C23AF0005EFA94 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E761FEA828C23AF0005EFA94 /* Assets.xcassets */; }; + E761FEAC28C23AF0005EFA94 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = E761FEAA28C23AF0005EFA94 /* MainMenu.xib */; }; + E79381F32CA72C6A005060BC /* RoyalVNCKit in Frameworks */ = {isa = PBXBuildFile; productRef = E79381F22CA72C6A005060BC /* RoyalVNCKit */; }; + E79EBE292902B5810013A7E4 /* ConfigurationView.xib in Resources */ = {isa = PBXBuildFile; fileRef = E79EBE262902B5270013A7E4 /* ConfigurationView.xib */; }; + E79EBE2A2902B5840013A7E4 /* EncodingsConfigurationView.xib in Resources */ = {isa = PBXBuildFile; fileRef = E79EBE202902B4D40013A7E4 /* EncodingsConfigurationView.xib */; }; + E7B3135A28E5C07200C40684 /* ConfigurationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7B3135928E5C07100C40684 /* ConfigurationViewController.swift */; }; + E7B3135E28E5C59300C40684 /* ConnectionView.xib in Resources */ = {isa = PBXBuildFile; fileRef = E7B3135D28E5C59300C40684 /* ConnectionView.xib */; }; + E7B3136028E5C5A200C40684 /* ConnectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7B3135F28E5C5A200C40684 /* ConnectionViewController.swift */; }; + E7B3136228E5CEB500C40684 /* ConnectionWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7B3136128E5CEB500C40684 /* ConnectionWindowController.swift */; }; + E7B3136428E5CEC100C40684 /* ConnectionWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = E7B3136328E5CEC100C40684 /* ConnectionWindow.xib */; }; + E7B3136628E5D52E00C40684 /* ConnectionViewControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7B3136528E5D52E00C40684 /* ConnectionViewControllerDelegate.swift */; }; + E7B3136828E5D5C400C40684 /* ConnectionWindowControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7B3136728E5D5C400C40684 /* ConnectionWindowControllerDelegate.swift */; }; + E7B94D2B28D20D1700AC827B /* CredentialWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7B94D2A28D20D1700AC827B /* CredentialWindowController.swift */; }; + E7B94D3128D20DAD00AC827B /* CredentialWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = E7B94D3028D20DAD00AC827B /* CredentialWindow.xib */; }; + E7E8BCC228F6BB1F00417728 /* EncodingsConfigurationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7E8BCC128F6BB1F00417728 /* EncodingsConfigurationViewController.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + E742BD1E29018500007D23D5 /* ConfigurationWindow.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ConfigurationWindow.xib; sourceTree = ""; }; + E742BD2029018513007D23D5 /* ConfigurationWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationWindowController.swift; sourceTree = ""; }; + E761FEA328C23AEF005EFA94 /* RoyalVNCDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RoyalVNCDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; + E761FEA628C23AEF005EFA94 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + E761FEA828C23AF0005EFA94 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + E761FEAB28C23AF0005EFA94 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + E761FEAD28C23AF0005EFA94 /* RoyalVNCDemo.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RoyalVNCDemo.entitlements; sourceTree = ""; }; + E761FEB428C23B04005EFA94 /* RoyalVNCKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = RoyalVNCKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + E79EBE202902B4D40013A7E4 /* EncodingsConfigurationView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = EncodingsConfigurationView.xib; sourceTree = ""; }; + E79EBE262902B5270013A7E4 /* ConfigurationView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ConfigurationView.xib; sourceTree = ""; }; + E7AE880A29150B5C001BAE18 /* Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + E7AE880D29151176001BAE18 /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + E7B3135928E5C07100C40684 /* ConfigurationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationViewController.swift; sourceTree = ""; }; + E7B3135D28E5C59300C40684 /* ConnectionView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ConnectionView.xib; sourceTree = ""; }; + E7B3135F28E5C5A200C40684 /* ConnectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionViewController.swift; sourceTree = ""; }; + E7B3136128E5CEB500C40684 /* ConnectionWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionWindowController.swift; sourceTree = ""; }; + E7B3136328E5CEC100C40684 /* ConnectionWindow.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ConnectionWindow.xib; sourceTree = ""; }; + E7B3136528E5D52E00C40684 /* ConnectionViewControllerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionViewControllerDelegate.swift; sourceTree = ""; }; + E7B3136728E5D5C400C40684 /* ConnectionWindowControllerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionWindowControllerDelegate.swift; sourceTree = ""; }; + E7B94D2A28D20D1700AC827B /* CredentialWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CredentialWindowController.swift; sourceTree = ""; }; + E7B94D3028D20DAD00AC827B /* CredentialWindow.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = CredentialWindow.xib; sourceTree = ""; }; + E7E8BCC128F6BB1F00417728 /* EncodingsConfigurationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncodingsConfigurationViewController.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + E761FEA028C23AEF005EFA94 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + E79381F32CA72C6A005060BC /* RoyalVNCKit in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + E761FE9A28C23AEF005EFA94 = { + isa = PBXGroup; + children = ( + E761FEA528C23AEF005EFA94 /* Source */, + E761FEA428C23AEF005EFA94 /* Products */, + E761FEB328C23B04005EFA94 /* Frameworks */, + ); + sourceTree = ""; + }; + E761FEA428C23AEF005EFA94 /* Products */ = { + isa = PBXGroup; + children = ( + E761FEA328C23AEF005EFA94 /* RoyalVNCDemo.app */, + ); + name = Products; + sourceTree = ""; + }; + E761FEA528C23AEF005EFA94 /* Source */ = { + isa = PBXGroup; + children = ( + E7B3136B28E5DDD200C40684 /* Connection */, + E7B3136A28E5DDC400C40684 /* Credential */, + E7B3136928E5DDB400C40684 /* Configuration */, + E761FEA628C23AEF005EFA94 /* AppDelegate.swift */, + E761FEAA28C23AF0005EFA94 /* MainMenu.xib */, + E761FEA828C23AF0005EFA94 /* Assets.xcassets */, + E761FEAD28C23AF0005EFA94 /* RoyalVNCDemo.entitlements */, + E7AE880A29150B5C001BAE18 /* Debug.xcconfig */, + E7AE880D29151176001BAE18 /* Release.xcconfig */, + ); + path = Source; + sourceTree = ""; + }; + E761FEB328C23B04005EFA94 /* Frameworks */ = { + isa = PBXGroup; + children = ( + E761FEB428C23B04005EFA94 /* RoyalVNCKit.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + E7B3136928E5DDB400C40684 /* Configuration */ = { + isa = PBXGroup; + children = ( + E742BD2029018513007D23D5 /* ConfigurationWindowController.swift */, + E742BD1E29018500007D23D5 /* ConfigurationWindow.xib */, + E7B3135928E5C07100C40684 /* ConfigurationViewController.swift */, + E79EBE262902B5270013A7E4 /* ConfigurationView.xib */, + E7E8BCC128F6BB1F00417728 /* EncodingsConfigurationViewController.swift */, + E79EBE202902B4D40013A7E4 /* EncodingsConfigurationView.xib */, + ); + path = Configuration; + sourceTree = ""; + }; + E7B3136A28E5DDC400C40684 /* Credential */ = { + isa = PBXGroup; + children = ( + E7B94D2A28D20D1700AC827B /* CredentialWindowController.swift */, + E7B94D3028D20DAD00AC827B /* CredentialWindow.xib */, + ); + path = Credential; + sourceTree = ""; + }; + E7B3136B28E5DDD200C40684 /* Connection */ = { + isa = PBXGroup; + children = ( + E7B3136128E5CEB500C40684 /* ConnectionWindowController.swift */, + E7B3136728E5D5C400C40684 /* ConnectionWindowControllerDelegate.swift */, + E7B3136328E5CEC100C40684 /* ConnectionWindow.xib */, + E7B3135F28E5C5A200C40684 /* ConnectionViewController.swift */, + E7B3136528E5D52E00C40684 /* ConnectionViewControllerDelegate.swift */, + E7B3135D28E5C59300C40684 /* ConnectionView.xib */, + ); + path = Connection; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + E761FEA228C23AEF005EFA94 /* RoyalVNCDemo */ = { + isa = PBXNativeTarget; + buildConfigurationList = E761FEB028C23AF0005EFA94 /* Build configuration list for PBXNativeTarget "RoyalVNCDemo" */; + buildPhases = ( + E761FE9F28C23AEF005EFA94 /* Sources */, + E761FEA028C23AEF005EFA94 /* Frameworks */, + E7B3134C28E5882200C40684 /* Swiftlint */, + E761FEA128C23AEF005EFA94 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = RoyalVNCDemo; + productName = RoyalVNCDemo; + productReference = E761FEA328C23AEF005EFA94 /* RoyalVNCDemo.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + E761FE9B28C23AEF005EFA94 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1340; + LastUpgradeCheck = 1600; + TargetAttributes = { + E761FEA228C23AEF005EFA94 = { + CreatedOnToolsVersion = 13.4.1; + }; + }; + }; + buildConfigurationList = E761FE9E28C23AEF005EFA94 /* Build configuration list for PBXProject "RoyalVNCDemo" */; + compatibilityVersion = "Xcode 13.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = E761FE9A28C23AEF005EFA94; + productRefGroup = E761FEA428C23AEF005EFA94 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + E761FEA228C23AEF005EFA94 /* RoyalVNCDemo */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + E761FEA128C23AEF005EFA94 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + E742BD1F29018500007D23D5 /* ConfigurationWindow.xib in Resources */, + E7B3135E28E5C59300C40684 /* ConnectionView.xib in Resources */, + E761FEA928C23AF0005EFA94 /* Assets.xcassets in Resources */, + E79EBE292902B5810013A7E4 /* ConfigurationView.xib in Resources */, + E79EBE2A2902B5840013A7E4 /* EncodingsConfigurationView.xib in Resources */, + E7B3136428E5CEC100C40684 /* ConnectionWindow.xib in Resources */, + E761FEAC28C23AF0005EFA94 /* MainMenu.xib in Resources */, + E7B94D3128D20DAD00AC827B /* CredentialWindow.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + E7B3134C28E5882200C40684 /* Swiftlint */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = Swiftlint; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "if which swiftlint >/dev/null; then\n\tswiftlint lint --config \"${PROJECT_DIR}/../.swiftlint.yml\"\nelse\n\techo \"Warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + E761FE9F28C23AEF005EFA94 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + E7B3135A28E5C07200C40684 /* ConfigurationViewController.swift in Sources */, + E7E8BCC228F6BB1F00417728 /* EncodingsConfigurationViewController.swift in Sources */, + E7B94D2B28D20D1700AC827B /* CredentialWindowController.swift in Sources */, + E742BD2129018513007D23D5 /* ConfigurationWindowController.swift in Sources */, + E7B3136228E5CEB500C40684 /* ConnectionWindowController.swift in Sources */, + E7B3136828E5D5C400C40684 /* ConnectionWindowControllerDelegate.swift in Sources */, + E761FEA728C23AEF005EFA94 /* AppDelegate.swift in Sources */, + E7B3136628E5D52E00C40684 /* ConnectionViewControllerDelegate.swift in Sources */, + E7B3136028E5C5A200C40684 /* ConnectionViewController.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + E761FEAA28C23AF0005EFA94 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + E761FEAB28C23AF0005EFA94 /* Base */, + ); + name = MainMenu.xib; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + E761FEAE28C23AF0005EFA94 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = E7AE880A29150B5C001BAE18 /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_ENTITLEMENTS = Source/RoyalVNCDemo.entitlements; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + E761FEAF28C23AF0005EFA94 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = E7AE880D29151176001BAE18 /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_ENTITLEMENTS = Source/RoyalVNCDemo.entitlements; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + E761FEB128C23AF0005EFA94 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = E7AE880A29150B5C001BAE18 /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEAD_CODE_STRIPPING = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_CFBundleDisplayName = "RoyalVNC Demo"; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; + INFOPLIST_KEY_NSHumanReadableCopyright = "© 2022 Royal Apps GmbH"; + INFOPLIST_KEY_NSMainNibFile = MainMenu; + INFOPLIST_KEY_NSPrincipalClass = NSApplication; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.royalapps.RoyalVNCDemo; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + E761FEB228C23AF0005EFA94 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = E7AE880D29151176001BAE18 /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEAD_CODE_STRIPPING = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_CFBundleDisplayName = "RoyalVNC Demo"; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; + INFOPLIST_KEY_NSHumanReadableCopyright = "© 2022 Royal Apps GmbH"; + INFOPLIST_KEY_NSMainNibFile = MainMenu; + INFOPLIST_KEY_NSPrincipalClass = NSApplication; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.royalapps.RoyalVNCDemo; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + E761FE9E28C23AEF005EFA94 /* Build configuration list for PBXProject "RoyalVNCDemo" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + E761FEAE28C23AF0005EFA94 /* Debug */, + E761FEAF28C23AF0005EFA94 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + E761FEB028C23AF0005EFA94 /* Build configuration list for PBXNativeTarget "RoyalVNCDemo" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + E761FEB128C23AF0005EFA94 /* Debug */, + E761FEB228C23AF0005EFA94 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCSwiftPackageProductDependency section */ + E79381F22CA72C6A005060BC /* RoyalVNCKit */ = { + isa = XCSwiftPackageProductDependency; + productName = RoyalVNCKit; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = E761FE9B28C23AEF005EFA94 /* Project object */; +} diff --git a/RoyalVNCDemo/RoyalVNCDemo.xcodeproj/xcshareddata/xcschemes/RoyalVNCDemo.xcscheme b/RoyalVNCDemo/RoyalVNCDemo.xcodeproj/xcshareddata/xcschemes/RoyalVNCDemo.xcscheme new file mode 100644 index 0000000..e98bfd4 --- /dev/null +++ b/RoyalVNCDemo/RoyalVNCDemo.xcodeproj/xcshareddata/xcschemes/RoyalVNCDemo.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/RoyalVNCDemo/Source/AppDelegate.swift b/RoyalVNCDemo/Source/AppDelegate.swift new file mode 100644 index 0000000..a4b41a3 --- /dev/null +++ b/RoyalVNCDemo/Source/AppDelegate.swift @@ -0,0 +1,36 @@ +import Foundation +import AppKit + +import RoyalVNCKit + +@main +@objc(AppDelegate) +class AppDelegate: NSObject, NSApplicationDelegate { + @IBOutlet private weak var menuConnection: NSMenu! + + @IBOutlet private weak var menuItemConnectionColorDepth8Bit: NSMenuItem! + @IBOutlet private weak var menuItemConnectionColorDepth16Bit: NSMenuItem! + @IBOutlet private weak var menuItemConnectionColorDepth24Bit: NSMenuItem! + + private let configurationWindowController = ConfigurationWindowController() + + func applicationDidFinishLaunching(_ aNotification: Notification) { + configurationWindowController.showWindow(self) + } + + func applicationWillTerminate(_ notification: Notification) { + configurationWindowController.saveSettings() + } +} + +extension AppDelegate: NSMenuDelegate { + func menuNeedsUpdate(_ menu: NSMenu) { + guard menu == menuConnection else { return } + + let colorDepth = configurationWindowController.colorDepthOfActiveConnection + + menuItemConnectionColorDepth8Bit.state = colorDepth == .depth8Bit ? .on : .off + menuItemConnectionColorDepth16Bit.state = colorDepth == .depth16Bit ? .on : .off + menuItemConnectionColorDepth24Bit.state = colorDepth == .depth24Bit ? .on : .off + } +} diff --git a/RoyalVNCDemo/Source/Assets.xcassets/AccentColor.colorset/Contents.json b/RoyalVNCDemo/Source/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..274babb --- /dev/null +++ b/RoyalVNCDemo/Source/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/RoyalVNCDemo/Source/Assets.xcassets/AppIcon.appiconset/AppIconMacOS_1024.png b/RoyalVNCDemo/Source/Assets.xcassets/AppIcon.appiconset/AppIconMacOS_1024.png new file mode 100644 index 0000000..5c286ba Binary files /dev/null and b/RoyalVNCDemo/Source/Assets.xcassets/AppIcon.appiconset/AppIconMacOS_1024.png differ diff --git a/RoyalVNCDemo/Source/Assets.xcassets/AppIcon.appiconset/AppIconMacOS_128.png b/RoyalVNCDemo/Source/Assets.xcassets/AppIcon.appiconset/AppIconMacOS_128.png new file mode 100644 index 0000000..b1c0cc2 Binary files /dev/null and b/RoyalVNCDemo/Source/Assets.xcassets/AppIcon.appiconset/AppIconMacOS_128.png differ diff --git a/RoyalVNCDemo/Source/Assets.xcassets/AppIcon.appiconset/AppIconMacOS_16.png b/RoyalVNCDemo/Source/Assets.xcassets/AppIcon.appiconset/AppIconMacOS_16.png new file mode 100644 index 0000000..43cfecf Binary files /dev/null and b/RoyalVNCDemo/Source/Assets.xcassets/AppIcon.appiconset/AppIconMacOS_16.png differ diff --git a/RoyalVNCDemo/Source/Assets.xcassets/AppIcon.appiconset/AppIconMacOS_256.png b/RoyalVNCDemo/Source/Assets.xcassets/AppIcon.appiconset/AppIconMacOS_256.png new file mode 100644 index 0000000..ebe5f6d Binary files /dev/null and b/RoyalVNCDemo/Source/Assets.xcassets/AppIcon.appiconset/AppIconMacOS_256.png differ diff --git a/RoyalVNCDemo/Source/Assets.xcassets/AppIcon.appiconset/AppIconMacOS_32.png b/RoyalVNCDemo/Source/Assets.xcassets/AppIcon.appiconset/AppIconMacOS_32.png new file mode 100644 index 0000000..d99c08a Binary files /dev/null and b/RoyalVNCDemo/Source/Assets.xcassets/AppIcon.appiconset/AppIconMacOS_32.png differ diff --git a/RoyalVNCDemo/Source/Assets.xcassets/AppIcon.appiconset/AppIconMacOS_512.png b/RoyalVNCDemo/Source/Assets.xcassets/AppIcon.appiconset/AppIconMacOS_512.png new file mode 100644 index 0000000..9b389b3 Binary files /dev/null and b/RoyalVNCDemo/Source/Assets.xcassets/AppIcon.appiconset/AppIconMacOS_512.png differ diff --git a/RoyalVNCDemo/Source/Assets.xcassets/AppIcon.appiconset/AppIconMacOS_64.png b/RoyalVNCDemo/Source/Assets.xcassets/AppIcon.appiconset/AppIconMacOS_64.png new file mode 100644 index 0000000..ae5a42e Binary files /dev/null and b/RoyalVNCDemo/Source/Assets.xcassets/AppIcon.appiconset/AppIconMacOS_64.png differ diff --git a/RoyalVNCDemo/Source/Assets.xcassets/AppIcon.appiconset/Contents.json b/RoyalVNCDemo/Source/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..b918312 --- /dev/null +++ b/RoyalVNCDemo/Source/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "filename" : "AppIconMacOS_16.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "16x16" + }, + { + "filename" : "AppIconMacOS_32.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "16x16" + }, + { + "filename" : "AppIconMacOS_32.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "32x32" + }, + { + "filename" : "AppIconMacOS_64.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "32x32" + }, + { + "filename" : "AppIconMacOS_128.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "128x128" + }, + { + "filename" : "AppIconMacOS_256.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "128x128" + }, + { + "filename" : "AppIconMacOS_256.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "256x256" + }, + { + "filename" : "AppIconMacOS_512.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "256x256" + }, + { + "filename" : "AppIconMacOS_512.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "512x512" + }, + { + "filename" : "AppIconMacOS_1024.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "512x512" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/RoyalVNCDemo/Source/Assets.xcassets/Contents.json b/RoyalVNCDemo/Source/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/RoyalVNCDemo/Source/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/RoyalVNCDemo/Source/Base.lproj/MainMenu.xib b/RoyalVNCDemo/Source/Base.lproj/MainMenu.xib new file mode 100644 index 0000000..5f96ab2 --- /dev/null +++ b/RoyalVNCDemo/Source/Base.lproj/MainMenu.xib @@ -0,0 +1,720 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Default + + + + + + + Left to Right + + + + + + + Right to Left + + + + + + + + + + + Default + + + + + + + Left to Right + + + + + + + Right to Left + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/RoyalVNCDemo/Source/Configuration/ConfigurationView.xib b/RoyalVNCDemo/Source/Configuration/ConfigurationView.xib new file mode 100644 index 0000000..8f2257a --- /dev/null +++ b/RoyalVNCDemo/Source/Configuration/ConfigurationView.xib @@ -0,0 +1,200 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/RoyalVNCDemo/Source/Configuration/ConfigurationViewController.swift b/RoyalVNCDemo/Source/Configuration/ConfigurationViewController.swift new file mode 100644 index 0000000..34d9781 --- /dev/null +++ b/RoyalVNCDemo/Source/Configuration/ConfigurationViewController.swift @@ -0,0 +1,155 @@ +import Foundation +import AppKit + +import RoyalVNCKit + +class ConfigurationViewController: NSViewController { + @IBOutlet private weak var textFieldHostname: NSTextField! + @IBOutlet private weak var textFieldPort: NSTextField! + + @IBOutlet private weak var checkBoxShared: NSButton! + @IBOutlet private weak var checkBoxClipboardRedirection: NSButton! + @IBOutlet private weak var checkBoxScaling: NSButton! + @IBOutlet private weak var checkBoxUseDisplayLink: NSButton! + @IBOutlet private weak var checkBoxDebugLogging: NSButton! + + @IBOutlet private weak var popupButtonInputMode: NSPopUpButton! + @IBOutlet private weak var popupButtonColorDepth: NSPopUpButton! + @IBOutlet private weak var placeholderViewEncodings: NSView! + + private var didLoad = false + private let initialSettings: VNCConnection.Settings + private let encodingsConfigurationViewController = EncodingsConfigurationViewController(supportedFrameEncodings: .default) + + override var acceptsFirstResponder: Bool { true } + + init(settings: VNCConnection.Settings) { + self.initialSettings = settings + + super.init(nibName: "ConfigurationView", bundle: .main) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + + guard !didLoad else { return } + didLoad = true + + let encodingsConfigurationView = encodingsConfigurationViewController.view + encodingsConfigurationView.frame = placeholderViewEncodings.bounds + + placeholderViewEncodings.addSubview(encodingsConfigurationView) + + settings = initialSettings + } + + override func becomeFirstResponder() -> Bool { + textFieldHostname.becomeFirstResponder() + } +} + +extension ConfigurationViewController { + var settings: VNCConnection.Settings { + get { + .init(isDebugLoggingEnabled: isDebugLoggingEnabled, + hostname: hostname, + port: port, + isShared: isShared, + isScalingEnabled: isScalingEnabled, + useDisplayLink: useDisplayLink, + inputMode: inputMode, + isClipboardRedirectionEnabled: isClipboardRedirectionEnabled, + colorDepth: colorDepth, + frameEncodings: frameEncodings) + } + set { + isDebugLoggingEnabled = newValue.isDebugLoggingEnabled + hostname = newValue.hostname + port = newValue.port + isShared = newValue.isShared + isScalingEnabled = newValue.isScalingEnabled + useDisplayLink = newValue.useDisplayLink + inputMode = newValue.inputMode + isClipboardRedirectionEnabled = newValue.isClipboardRedirectionEnabled + colorDepth = newValue.colorDepth + frameEncodings = newValue.frameEncodings + } + } +} + +private extension ConfigurationViewController { + var isDebugLoggingEnabled: Bool { + get { checkBoxDebugLogging.state == .on } + set { checkBoxDebugLogging.state = newValue ? .on : .off } + } + + var hostname: String { + get { textFieldHostname.stringValue } + set { textFieldHostname.stringValue = newValue } + } + + var port: UInt16 { + get { .init(textFieldPort.integerValue) } + set { textFieldPort.integerValue = .init(newValue) } + } + + var isShared: Bool { + get { checkBoxShared.state == .on } + set { checkBoxShared.state = newValue ? .on : .off } + } + + var isScalingEnabled: Bool { + get { checkBoxScaling.state == .on } + set { checkBoxScaling.state = newValue ? .on : .off } + } + + var useDisplayLink: Bool { + get { checkBoxUseDisplayLink.state == .on } + set { checkBoxUseDisplayLink.state = newValue ? .on : .off } + } + + var inputMode: VNCConnection.Settings.InputMode { + get { .init(rawValue: .init(popupButtonInputMode.indexOfSelectedItem)) ?? .forwardKeyboardShortcutsIfNotInUseLocally } + set { popupButtonInputMode.selectItem(at: .init(newValue.rawValue)) } + } + + var isClipboardRedirectionEnabled: Bool { + get { checkBoxClipboardRedirection.state == .on } + set { checkBoxClipboardRedirection.state = newValue ? .on : .off } + } + + var colorDepth: VNCConnection.Settings.ColorDepth { + get { + switch popupButtonColorDepth.indexOfSelectedItem { + case 0: + return .depth8Bit + case 1: + return .depth16Bit + case 2: + return .depth24Bit + default: + return .depth24Bit + } + } + set { + switch newValue { + case .depth8Bit: + popupButtonColorDepth.selectItem(at: 0) + case .depth16Bit: + popupButtonColorDepth.selectItem(at: 1) + case .depth24Bit: + popupButtonColorDepth.selectItem(at: 2) + } + } + } + + var frameEncodings: [VNCFrameEncodingType] { + get { encodingsConfigurationViewController.frameEncodings } + set { encodingsConfigurationViewController.frameEncodings = newValue } + } +} diff --git a/RoyalVNCDemo/Source/Configuration/ConfigurationWindow.xib b/RoyalVNCDemo/Source/Configuration/ConfigurationWindow.xib new file mode 100644 index 0000000..c486929 --- /dev/null +++ b/RoyalVNCDemo/Source/Configuration/ConfigurationWindow.xib @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/RoyalVNCDemo/Source/Configuration/ConfigurationWindowController.swift b/RoyalVNCDemo/Source/Configuration/ConfigurationWindowController.swift new file mode 100644 index 0000000..5d714e3 --- /dev/null +++ b/RoyalVNCDemo/Source/Configuration/ConfigurationWindowController.swift @@ -0,0 +1,131 @@ +import Foundation +import AppKit + +import RoyalVNCKit + +class ConfigurationWindowController: NSWindowController { + @IBOutlet private weak var viewPlaceholderConfiguration: NSView! + @IBOutlet private weak var buttonConnect: NSButton! + + override var windowNibName: NSNib.Name? { "ConfigurationWindow" } + + private var didLoad = false + + private let configurationViewController = ConfigurationViewController(settings: .fromUserDefaults()) + private var connectionWindowControllers = [ConnectionWindowController]() + + // MARK: - UI Events + override func windowDidLoad() { + super.windowDidLoad() + + guard !didLoad else { return } + didLoad = true + + configureUI() + } + + @IBAction private func buttonConnect_action(_ sender: Any) { + connect() + } +} + +extension ConfigurationWindowController { + func saveSettings() { + configurationViewController.settings.saveToUserDefaults() + } + + var colorDepthOfActiveConnection: VNCConnection.Settings.ColorDepth? { + guard let activeConnection = activeConnection else { + return nil + } + + guard let framebuffer = activeConnection.framebuffer else { + return nil + } + + let colorDepth = framebuffer.colorDepth + + return colorDepth + } +} + +private extension ConfigurationWindowController { + func configureUI() { + let configView = configurationViewController.view + configView.frame = viewPlaceholderConfiguration.bounds + configView.autoresizingMask = [ .minXMargin, .maxXMargin, .minYMargin, .maxYMargin, .width, .height ] + + viewPlaceholderConfiguration.addSubview(configView) + + window?.recalculateKeyViewLoop() + window?.makeFirstResponder(configurationViewController) + } + + func connect() { + guard let window = window else { return } + + let settings = configurationViewController.settings + settings.saveToUserDefaults() + + if settings.inputMode.requiresAccessibilityPermissions && + !VNCAccessibilityUtils.hasAccessibilityPermissions { + // Requires accessibility permissions but don't have them right now so ask user + + let alert = NSAlert() + alert.messageText = "Accessibility Permissions" + alert.informativeText = "Accessibility Permissions are required when the input mode is set to \"Forward all keyboard shortcuts and hot keys\". To continue, please open System Settings and grant the app accessibility permissions." + + alert.addButton(withTitle: "Open System Settings") + alert.addButton(withTitle: "Continue without permissions") + alert.addButton(withTitle: "Cancel") + + alert.beginSheetModal(for: window) { [weak self] response in + switch response { + case .alertFirstButtonReturn: + VNCAccessibilityUtils.openAccessibilityPermissionsPreferencePane() + case .alertSecondButtonReturn: + self?.connect(settings: settings) + default: + return + } + + } + } else { + connect(settings: settings) + } + } + + func connect(settings: VNCConnection.Settings) { + let connectionWindowController = ConnectionWindowController(settings: settings) + connectionWindowController.delegate = self + + connectionWindowControllers.append(connectionWindowController) + + connectionWindowController.showWindow(self) + connectionWindowController.connect() + } + + var activeConnection: VNCConnection? { + return activeConnectionWindowController?.connection + } + + var activeConnectionWindowController: ConnectionWindowController? { + guard let keyWindow = NSApplication.shared.keyWindow else { + return nil + } + + guard let keyConnectionWindowController = connectionWindowControllers.first(where: { $0.window == keyWindow }) else { + return nil + } + + return keyConnectionWindowController + } +} + +extension ConfigurationWindowController: ConnectionWindowControllerDelegate { + func connectionWindowControllerDidClose(_ connectionWindowController: ConnectionWindowController) { + connectionWindowController.delegate = nil + + connectionWindowControllers.removeAll { $0 == connectionWindowController } + } +} diff --git a/RoyalVNCDemo/Source/Configuration/EncodingsConfigurationView.xib b/RoyalVNCDemo/Source/Configuration/EncodingsConfigurationView.xib new file mode 100644 index 0000000..fce6347 --- /dev/null +++ b/RoyalVNCDemo/Source/Configuration/EncodingsConfigurationView.xib @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/RoyalVNCDemo/Source/Configuration/EncodingsConfigurationViewController.swift b/RoyalVNCDemo/Source/Configuration/EncodingsConfigurationViewController.swift new file mode 100644 index 0000000..65edd85 --- /dev/null +++ b/RoyalVNCDemo/Source/Configuration/EncodingsConfigurationViewController.swift @@ -0,0 +1,160 @@ +import Foundation +import AppKit + +import RoyalVNCKit + +class EncodingsConfigurationViewController: NSViewController { + @IBOutlet private weak var tableView: NSTableView! + + private var didLoad = false + + let supportedFrameEncodings: [VNCFrameEncodingType] + + var frameEncodings: [VNCFrameEncodingType] { + didSet { + tableView.reloadData() + } + } + + private var orderedFrameEncodings: [VNCFrameEncodingType] { + let encs = orderedEncodings(encodings: supportedFrameEncodings, + order: frameEncodings) + + return encs + } + + init(supportedFrameEncodings: [VNCFrameEncodingType]) { + self.supportedFrameEncodings = supportedFrameEncodings + self.frameEncodings = supportedFrameEncodings + + super.init(nibName: "EncodingsConfigurationView", bundle: .main) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + + guard !didLoad else { return } + didLoad = true + + tableView.dataSource = self + tableView.delegate = self + } + + @IBAction private func checkBoxEncoding_action(_ sender: NSButton) { + let row = tableView.row(for: sender) + + guard row != NSNotFound, + row >= 0 else { + return + } + + let encoding = orderedFrameEncodings[row] + let isEnabled = sender.state == .on + + setEncoding(encoding, + index: row, + enabled: isEnabled) + } + + @IBAction private func buttonMoveUp_action(_ sender: NSButton) { + let row = tableView.selectedRow + + guard row != NSNotFound, + row >= 0 else { + return + } + + moveEncoding(atIndex: row, + up: true) + } + + @IBAction private func buttonMoveDown_action(_ sender: NSButton) { + let row = tableView.selectedRow + + guard row != NSNotFound, + row >= 0 else { + return + } + + moveEncoding(atIndex: row, + up: false) + } + + func orderedEncodings(encodings: [VNCFrameEncodingType], + order: [VNCFrameEncodingType]) -> [VNCFrameEncodingType] { + order.filter { encodings.contains($0) } + encodings.filter { !order.contains($0) } + } + + func setEncoding(_ encoding: VNCFrameEncodingType, + index: Int, + enabled isEnabled: Bool) { + if isEnabled { + if index < frameEncodings.count { + frameEncodings.insert(encoding, at: index) + } else { + frameEncodings.append(encoding) + } + } else { + frameEncodings.remove(at: index) + } + + tableView.reloadData() + } + + func moveEncoding(atIndex index: Int, + up: Bool) { + guard index >= 0, + index < frameEncodings.count else { + return + } + + let newIndex = up + ? max(index - 1, 0) + : min(index + 1, frameEncodings.count - 1) + + let encoding = frameEncodings.remove(at: index) + + frameEncodings.insert(encoding, at: newIndex) + + tableView.reloadData() + tableView.selectRowIndexes(.init(integer: newIndex), byExtendingSelection: false) + } +} + +extension EncodingsConfigurationViewController: NSTableViewDelegate, NSTableViewDataSource { + func numberOfRows(in tableView: NSTableView) -> Int { + orderedFrameEncodings.count + } + + func tableView(_ tableView: NSTableView, + viewFor tableColumn: NSTableColumn?, + row: Int) -> NSView? { + let encoding = orderedFrameEncodings[row] + + guard let cellView = tableView.makeView(withIdentifier: .init("EncodingCell"), owner: nil), + let checkBox = cellView.viewWithTag(450) as? NSButton else { + return nil + } + + let title = encoding.description + let isEnabled = frameEncodings.contains(encoding) + + checkBox.title = title + checkBox.state = isEnabled ? .on : .off + + checkBox.target = self + checkBox.action = #selector(checkBoxEncoding_action(_:)) + + return cellView + } + + func tableView(_ tableView: NSTableView, + isGroupRow row: Int) -> Bool { + return false + } +} diff --git a/RoyalVNCDemo/Source/Connection/ConnectionView.xib b/RoyalVNCDemo/Source/Connection/ConnectionView.xib new file mode 100644 index 0000000..346a12b --- /dev/null +++ b/RoyalVNCDemo/Source/Connection/ConnectionView.xib @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/RoyalVNCDemo/Source/Connection/ConnectionViewController.swift b/RoyalVNCDemo/Source/Connection/ConnectionViewController.swift new file mode 100644 index 0000000..9d83113 --- /dev/null +++ b/RoyalVNCDemo/Source/Connection/ConnectionViewController.swift @@ -0,0 +1,316 @@ +import Foundation +import AppKit + +import RoyalVNCKit + +class ConnectionViewController: NSViewController { + @IBOutlet private weak var progressIndicator: NSProgressIndicator! + @IBOutlet private weak var textFieldStatus: NSTextField! + @IBOutlet private weak var framebufferScrollView: VNCScrollView! + + weak var delegate: ConnectionViewControllerDelegate? + + let connection: VNCConnection + + private var framebufferView: VNCCAFramebufferView? + private var credentialWindowController: CredentialWindowController? + + init(settings: VNCConnection.Settings) { + self.connection = .init(settings: settings) + + super.init(nibName: "ConnectionView", bundle: .main) + + self.connection.delegate = self + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + connection.delegate = nil + } +} + +extension ConnectionViewController { + func connect() { + connection.connect() + } + + func disconnect() { + guard connection.connectionState.status != .disconnected else { return } + + connection.disconnect() + } +} + +private extension ConnectionViewController { + func showProgress(statusText: String) { + progressIndicator.startAnimation(nil) + progressIndicator.isHidden = false + + textFieldStatus.stringValue = statusText + textFieldStatus.isHidden = false + } + + func hideProgress() { + progressIndicator.stopAnimation(nil) + progressIndicator.isHidden = true + + textFieldStatus.isHidden = true + } + + func createFramebufferView(connection: VNCConnection, + framebuffer: VNCFramebuffer) { + createFramebufferView(connection: connection, + framebuffer: framebuffer, + in: framebufferScrollView, + makeFirstResponder: true) + } + + @discardableResult + func createFramebufferView(connection: VNCConnection, + framebuffer: VNCFramebuffer, + in scrollView: VNCScrollView, + makeFirstResponder: Bool) -> VNCFramebufferView { + destroyFramebufferView() + + let isScalingEnabled = connection.settings.isScalingEnabled + + let viewSize = isScalingEnabled + ? scrollView.bounds.size + : framebuffer.size.cgSize + + let rect = CGRect(origin: .zero, + size: viewSize) + + let view = VNCCAFramebufferView(frame: rect, + framebuffer: framebuffer, + connection: connection) + + if isScalingEnabled { + view.autoresizingMask = [ + .minXMargin, .maxXMargin, + .minYMargin, .maxYMargin, + .width, .height + ] + + scrollView.hasVerticalScroller = false + scrollView.hasHorizontalScroller = false + } else { + scrollView.hasVerticalScroller = true + scrollView.hasHorizontalScroller = true + } + + scrollView.documentView = view + + framebufferView = view + + if makeFirstResponder { + view.window?.makeFirstResponder(view) + } + + return view + } + + func destroyFramebufferView() { + guard let framebufferView = framebufferView else { return } + + framebufferView.removeFromSuperview() + + self.framebufferView = nil + } + + func handleConnectionStateDidChange(_ connectionState: VNCConnection.ConnectionState) { + let statusText: String? + + var didCloseConnection = false + + switch connectionState.status { + case .connecting: + statusText = "Connecting…" + case .disconnecting: + statusText = "Disconnecting…" + case .connected: + statusText = nil + case .disconnected: + statusText = nil + + didCloseConnection = true + } + + if let statusText = statusText { + showProgress(statusText: statusText) + } else { + hideProgress() + } + + guard didCloseConnection else { + return + } + + handleConnectionDidClose(error: connectionState.error) + } + + func handleConnectionDidClose(error: Error?) { + connection.delegate = nil + + destroyFramebufferView() + + presentError(error) { + self.delegate?.connectionViewControllerDidDisconnect(self) + } + } + + func presentError(_ error: Error?, + completion: @escaping () -> Void) { + guard let error = error else { + completion() + + return + } + + let vncError = error as? VNCError + let shouldDisplayError = vncError?.shouldDisplayToUser ?? true + + guard shouldDisplayError else { + completion() + + return + } + + let errorText = error.localizedDescription + + let alert = NSAlert() + alert.alertStyle = .warning + alert.messageText = "Disconnected with Error" + alert.informativeText = errorText + + alert.addButton(withTitle: "OK") + + if let window = view.window { + alert.beginSheetModal(for: window) { _ in + completion() + } + } else { + alert.runModal() + + completion() + } + } + + func credentialFor(authenticationType: VNCAuthenticationType, + completion: @escaping (VNCCredential?) -> Void) { + guard let window = view.window else { + completion(nil) + + return + } + + let settings = connection.settings + + let cachedUsername = settings.cachedUsername + let cachedPassword = settings.cachedPassword + + let windowController = CredentialWindowController(authenticationType: authenticationType, + previousUsername: cachedUsername, + previousPassword: cachedPassword) + + credentialWindowController = windowController + + windowController.beginSheet(parentWindow: window) { [weak self] credential in + self?.credentialWindowController = nil + + if let credential = credential { + if let userPassCred = credential as? VNCUsernamePasswordCredential { + if userPassCred.username != cachedUsername { + settings.cachedUsername = userPassCred.username + } + + if userPassCred.password != cachedPassword { + settings.cachedPassword = userPassCred.password + } + } else if let passCred = credential as? VNCPasswordCredential { + if passCred.password != cachedPassword { + settings.cachedPassword = passCred.password + } + } + } + + completion(credential) + } + } +} + +extension ConnectionViewController { + @IBAction private func setColorDepth8Bit(_ sender: Any) { + connection.updateColorDepth(.depth8Bit) + } + + @IBAction private func setColorDepth16Bit(_ sender: Any) { + connection.updateColorDepth(.depth16Bit) + } + + @IBAction private func setColorDepth24Bit(_ sender: Any) { + connection.updateColorDepth(.depth24Bit) + } +} + +extension ConnectionViewController: VNCConnectionDelegate { + func connection(_ connection: VNCConnection, + stateDidChange connectionState: VNCConnection.ConnectionState) { + DispatchQueue.main.async { [weak self] in + self?.handleConnectionStateDidChange(connectionState) + } + } + + func connection(_ connection: VNCConnection, + credentialFor authenticationType: VNCAuthenticationType, + completion: @escaping (VNCCredential?) -> Void) { + DispatchQueue.main.async { [weak self] in + guard let strongSelf = self else { + completion(nil) + + return + } + + strongSelf.credentialFor(authenticationType: authenticationType, + completion: completion) + } + } + + func connection(_ connection: VNCConnection, + didCreateFramebuffer framebuffer: VNCFramebuffer) { + DispatchQueue.main.async { [weak self] in + guard let strongSelf = self else { return } + + strongSelf.createFramebufferView(connection: connection, + framebuffer: framebuffer) + } + } + + func connection(_ connection: VNCConnection, + didResizeFramebuffer framebuffer: VNCFramebuffer) { + DispatchQueue.main.async { [weak self] in + guard let strongSelf = self else { return } + + strongSelf.createFramebufferView(connection: connection, + framebuffer: framebuffer) + } + } + + func connection(_ connection: VNCConnection, + framebuffer: VNCFramebuffer, + didUpdateRegion updatedRegion: CGRect) { + framebufferView?.connection(connection, + framebuffer: framebuffer, + didUpdateRegion: updatedRegion) + } + + func connection(_ connection: VNCConnection, + didUpdateCursor cursor: VNCCursor) { + framebufferView?.connection(connection, + didUpdateCursor: cursor) + } +} diff --git a/RoyalVNCDemo/Source/Connection/ConnectionViewControllerDelegate.swift b/RoyalVNCDemo/Source/Connection/ConnectionViewControllerDelegate.swift new file mode 100644 index 0000000..5bd7049 --- /dev/null +++ b/RoyalVNCDemo/Source/Connection/ConnectionViewControllerDelegate.swift @@ -0,0 +1,5 @@ +import Foundation + +protocol ConnectionViewControllerDelegate: AnyObject { + func connectionViewControllerDidDisconnect(_ connectionViewController: ConnectionViewController) +} diff --git a/RoyalVNCDemo/Source/Connection/ConnectionWindow.xib b/RoyalVNCDemo/Source/Connection/ConnectionWindow.xib new file mode 100644 index 0000000..e487869 --- /dev/null +++ b/RoyalVNCDemo/Source/Connection/ConnectionWindow.xib @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/RoyalVNCDemo/Source/Connection/ConnectionWindowController.swift b/RoyalVNCDemo/Source/Connection/ConnectionWindowController.swift new file mode 100644 index 0000000..191c37d --- /dev/null +++ b/RoyalVNCDemo/Source/Connection/ConnectionWindowController.swift @@ -0,0 +1,79 @@ +import Foundation +import AppKit + +import RoyalVNCKit + +class ConnectionWindowController: NSWindowController { + override var windowNibName: NSNib.Name? { "ConnectionWindow" } + + let settings: VNCConnection.Settings + weak var delegate: ConnectionWindowControllerDelegate? + + var connection: VNCConnection { connectionViewController.connection } + + private var didLoad = false + + private let connectionViewController: ConnectionViewController + + init(settings: VNCConnection.Settings) { + self.settings = settings + connectionViewController = .init(settings: settings) + + super.init(window: nil) + + connectionViewController.delegate = self + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + connectionViewController.delegate = nil + } + + override func windowDidLoad() { + guard !didLoad else { return } + didLoad = true + + guard let window = window, + let contentView = window.contentView else { + return + } + + window.title = "Connection to \(settings.hostname)" + + let connectionView = connectionViewController.view + connectionView.frame = contentView.bounds + connectionView.autoresizingMask = [ .minXMargin, .maxXMargin, .minYMargin, .maxYMargin, .width, .height ] + + contentView.addSubview(connectionView) + } + + func connect() { + connectionViewController.connect() + } + + func disconnect() { + connectionViewController.disconnect() + } +} + +extension ConnectionWindowController: NSWindowDelegate { + func windowShouldClose(_ sender: NSWindow) -> Bool { + disconnect() + + return false + } +} + +extension ConnectionWindowController: ConnectionViewControllerDelegate { + func connectionViewControllerDidDisconnect(_ connectionViewController: ConnectionViewController) { + connectionViewController.delegate = nil + + close() + + delegate?.connectionWindowControllerDidClose(self) + } +} diff --git a/RoyalVNCDemo/Source/Connection/ConnectionWindowControllerDelegate.swift b/RoyalVNCDemo/Source/Connection/ConnectionWindowControllerDelegate.swift new file mode 100644 index 0000000..b17c33d --- /dev/null +++ b/RoyalVNCDemo/Source/Connection/ConnectionWindowControllerDelegate.swift @@ -0,0 +1,5 @@ +import Foundation + +protocol ConnectionWindowControllerDelegate: AnyObject { + func connectionWindowControllerDidClose(_ connectionWindowController: ConnectionWindowController) +} diff --git a/RoyalVNCDemo/Source/Credential/CredentialWindow.xib b/RoyalVNCDemo/Source/Credential/CredentialWindow.xib new file mode 100644 index 0000000..9a8eddb --- /dev/null +++ b/RoyalVNCDemo/Source/Credential/CredentialWindow.xib @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NSAllRomanInputSourcesLocaleIdentifier + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/RoyalVNCDemo/Source/Credential/CredentialWindowController.swift b/RoyalVNCDemo/Source/Credential/CredentialWindowController.swift new file mode 100644 index 0000000..ff85146 --- /dev/null +++ b/RoyalVNCDemo/Source/Credential/CredentialWindowController.swift @@ -0,0 +1,124 @@ +import Foundation +import AppKit +import RoyalVNCKit + +class CredentialWindowController: NSWindowController { + let authenticationType: VNCAuthenticationType + + let previousUsername: String + let previousPassword: String + + private var parentWindow: NSWindow? + private var didLoad = false + + @IBOutlet private weak var textFieldUsername: NSTextField! + @IBOutlet private weak var textFieldPassword: NSSecureTextField! + + @IBOutlet private weak var buttonOK: NSButton! + @IBOutlet private weak var buttonCancel: NSButton! + + override var windowNibName: NSNib.Name? { "CredentialWindow" } + + init(authenticationType: VNCAuthenticationType, + previousUsername: String, + previousPassword: String) { + self.authenticationType = authenticationType + + self.previousUsername = previousUsername + self.previousPassword = previousPassword + + super.init(window: nil) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - UI Events + override func windowDidLoad() { + super.windowDidLoad() + + guard !didLoad else { return } + didLoad = true + + configureUI() + } + + @IBAction private func buttonOK_action(_ sender: NSButton) { + handleButtonClicked(.OK) + } + + @IBAction private func buttonCancel_action(_ sender: NSButton) { + handleButtonClicked(.cancel) + } +} + +extension CredentialWindowController { + func beginSheet(parentWindow: NSWindow, + completion: @escaping (VNCCredential?) -> Void) { + guard let window = window else { + completion(nil) + + return + } + + self.parentWindow = parentWindow + + parentWindow.beginSheet(window) { [weak self] modalResponse in + guard let strongSelf = self, + modalResponse == .OK else { + completion(nil) + + return + } + + completion(strongSelf.credential) + } + } +} + +private extension CredentialWindowController { + func configureUI() { + textFieldUsername?.isEnabled = authenticationType.requiresUsername + textFieldPassword?.isEnabled = authenticationType.requiresPassword + + username = previousUsername + password = previousPassword + + if authenticationType.requiresUsername { + window?.makeFirstResponder(textFieldUsername) + } else { + window?.makeFirstResponder(textFieldPassword) + } + } + + func handleButtonClicked(_ modalResponse: NSApplication.ModalResponse) { + guard let parentWindow = parentWindow, + let window = window else { + return + } + + parentWindow.endSheet(window, + returnCode: modalResponse) + } + + var username: String { + get { textFieldUsername?.stringValue ?? "" } + set { textFieldUsername?.stringValue = newValue } + } + + var password: String { + get { textFieldPassword?.stringValue ?? "" } + set { textFieldPassword?.stringValue = newValue } + } + + var credential: VNCCredential { + if authenticationType.requiresUsername { + return VNCUsernamePasswordCredential(username: username, + password: password) + } else { + return VNCPasswordCredential(password: password) + } + } +} diff --git a/RoyalVNCDemo/Source/Debug.xcconfig b/RoyalVNCDemo/Source/Debug.xcconfig new file mode 100644 index 0000000..3455bcc --- /dev/null +++ b/RoyalVNCDemo/Source/Debug.xcconfig @@ -0,0 +1 @@ +#include? "../Debug.xcconfig" diff --git a/RoyalVNCDemo/Source/Release.xcconfig b/RoyalVNCDemo/Source/Release.xcconfig new file mode 100644 index 0000000..f849f27 --- /dev/null +++ b/RoyalVNCDemo/Source/Release.xcconfig @@ -0,0 +1 @@ +#include? "../Release.xcconfig" diff --git a/RoyalVNCDemo/Source/RoyalVNCDemo.entitlements b/RoyalVNCDemo/Source/RoyalVNCDemo.entitlements new file mode 100644 index 0000000..8cc185a --- /dev/null +++ b/RoyalVNCDemo/Source/RoyalVNCDemo.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.cs.disable-library-validation + + + diff --git a/RoyalVNCObjCDemo/RoyalVNCObjCDemo.xcodeproj/project.pbxproj b/RoyalVNCObjCDemo/RoyalVNCObjCDemo.xcodeproj/project.pbxproj new file mode 100644 index 0000000..ddfeecd --- /dev/null +++ b/RoyalVNCObjCDemo/RoyalVNCObjCDemo.xcodeproj/project.pbxproj @@ -0,0 +1,458 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 56; + objects = { + +/* Begin PBXBuildFile section */ + E742BD0A29018431007D23D5 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = E742BD0929018431007D23D5 /* AppDelegate.m */; }; + E742BD1129018432007D23D5 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = E742BD1029018432007D23D5 /* main.m */; }; + E742BD26290187A1007D23D5 /* CredentialWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = E742BD25290187A1007D23D5 /* CredentialWindow.xib */; }; + E742BD29290187D4007D23D5 /* CredentialWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = E742BD28290187D4007D23D5 /* CredentialWindowController.m */; }; + E742BD2B290191D7007D23D5 /* ConfigurationWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = E742BD2A290191D7007D23D5 /* ConfigurationWindow.xib */; }; + E742BD2E290191EB007D23D5 /* ConfigurationWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = E742BD2D290191EB007D23D5 /* ConfigurationWindowController.m */; }; + E742BD322901942B007D23D5 /* ConfigurationViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = E742BD312901942B007D23D5 /* ConfigurationViewController.m */; }; + E77D15B4290C175D000FDDC4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E77D15B3290C175D000FDDC4 /* Assets.xcassets */; }; + E79381F52CA72CBF005060BC /* RoyalVNCKit in Frameworks */ = {isa = PBXBuildFile; productRef = E79381F42CA72CBF005060BC /* RoyalVNCKit */; }; + E79EBE282902B5460013A7E4 /* ConfigurationView.xib in Resources */ = {isa = PBXBuildFile; fileRef = E79EBE272902B5460013A7E4 /* ConfigurationView.xib */; }; + E79EBE2D2902B5CC0013A7E4 /* EncodingsConfigurationViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = E79EBE2C2902B5CC0013A7E4 /* EncodingsConfigurationViewController.m */; }; + E79EBE2E2902B8760013A7E4 /* EncodingsConfigurationView.xib in Resources */ = {isa = PBXBuildFile; fileRef = E79EBE1F2902B4C10013A7E4 /* EncodingsConfigurationView.xib */; }; + E79EBE302902C36D0013A7E4 /* ConnectionView.xib in Resources */ = {isa = PBXBuildFile; fileRef = E79EBE2F2902C36D0013A7E4 /* ConnectionView.xib */; }; + E79EBE322902C3750013A7E4 /* ConnectionWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = E79EBE312902C3750013A7E4 /* ConnectionWindow.xib */; }; + E79EBE352902C38C0013A7E4 /* ConnectionViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = E79EBE342902C38C0013A7E4 /* ConnectionViewController.m */; }; + E79EBE382902C3AD0013A7E4 /* ConnectionWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = E79EBE372902C3AD0013A7E4 /* ConnectionWindowController.m */; }; + E7B4E3D3290BD1BC00FB35E4 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = E7B4E3D1290BD1BC00FB35E4 /* MainMenu.xib */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + E742BD0529018431007D23D5 /* RoyalVNCObjCDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RoyalVNCObjCDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; + E742BD0829018431007D23D5 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + E742BD0929018431007D23D5 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + E742BD1029018432007D23D5 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + E742BD1229018432007D23D5 /* RoyalVNCObjCDemo.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RoyalVNCObjCDemo.entitlements; sourceTree = ""; }; + E742BD1929018467007D23D5 /* RoyalVNCKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = RoyalVNCKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + E742BD25290187A1007D23D5 /* CredentialWindow.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = CredentialWindow.xib; path = ../../../RoyalVNCDemo/Source/Credential/CredentialWindow.xib; sourceTree = ""; }; + E742BD27290187D4007D23D5 /* CredentialWindowController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CredentialWindowController.h; sourceTree = ""; }; + E742BD28290187D4007D23D5 /* CredentialWindowController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CredentialWindowController.m; sourceTree = ""; }; + E742BD2A290191D7007D23D5 /* ConfigurationWindow.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = ConfigurationWindow.xib; path = ../../../RoyalVNCDemo/Source/Configuration/ConfigurationWindow.xib; sourceTree = ""; }; + E742BD2C290191EB007D23D5 /* ConfigurationWindowController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ConfigurationWindowController.h; sourceTree = ""; }; + E742BD2D290191EB007D23D5 /* ConfigurationWindowController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ConfigurationWindowController.m; sourceTree = ""; }; + E742BD302901942B007D23D5 /* ConfigurationViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ConfigurationViewController.h; sourceTree = ""; }; + E742BD312901942B007D23D5 /* ConfigurationViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ConfigurationViewController.m; sourceTree = ""; }; + E77D15B3290C175D000FDDC4 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = ../../RoyalVNCDemo/Source/Assets.xcassets; sourceTree = ""; }; + E79EBE1F2902B4C10013A7E4 /* EncodingsConfigurationView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = EncodingsConfigurationView.xib; path = ../../../RoyalVNCDemo/Source/Configuration/EncodingsConfigurationView.xib; sourceTree = ""; }; + E79EBE272902B5460013A7E4 /* ConfigurationView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = ConfigurationView.xib; path = ../../../RoyalVNCDemo/Source/Configuration/ConfigurationView.xib; sourceTree = ""; }; + E79EBE2B2902B5CC0013A7E4 /* EncodingsConfigurationViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = EncodingsConfigurationViewController.h; sourceTree = ""; }; + E79EBE2C2902B5CC0013A7E4 /* EncodingsConfigurationViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = EncodingsConfigurationViewController.m; sourceTree = ""; }; + E79EBE2F2902C36D0013A7E4 /* ConnectionView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = ConnectionView.xib; path = ../../../RoyalVNCDemo/Source/Connection/ConnectionView.xib; sourceTree = ""; }; + E79EBE312902C3750013A7E4 /* ConnectionWindow.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = ConnectionWindow.xib; path = ../../../RoyalVNCDemo/Source/Connection/ConnectionWindow.xib; sourceTree = ""; }; + E79EBE332902C38C0013A7E4 /* ConnectionViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ConnectionViewController.h; sourceTree = ""; }; + E79EBE342902C38C0013A7E4 /* ConnectionViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ConnectionViewController.m; sourceTree = ""; }; + E79EBE362902C3AD0013A7E4 /* ConnectionWindowController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ConnectionWindowController.h; sourceTree = ""; }; + E79EBE372902C3AD0013A7E4 /* ConnectionWindowController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ConnectionWindowController.m; sourceTree = ""; }; + E79EBE392902C3CD0013A7E4 /* ConnectionWindowControllerDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ConnectionWindowControllerDelegate.h; sourceTree = ""; }; + E79EBE3A2902C3E20013A7E4 /* ConnectionViewControllerDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ConnectionViewControllerDelegate.h; sourceTree = ""; }; + E7AE880B29150EBC001BAE18 /* Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + E7AE880E29151183001BAE18 /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + E7B4E3D2290BD1BC00FB35E4 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = ../../RoyalVNCDemo/Source/Base.lproj/MainMenu.xib; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + E742BD0229018431007D23D5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + E79381F52CA72CBF005060BC /* RoyalVNCKit in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + E742BCFC29018431007D23D5 = { + isa = PBXGroup; + children = ( + E742BD0729018431007D23D5 /* Source */, + E742BD0629018431007D23D5 /* Products */, + E742BD1829018467007D23D5 /* Frameworks */, + ); + sourceTree = ""; + }; + E742BD0629018431007D23D5 /* Products */ = { + isa = PBXGroup; + children = ( + E742BD0529018431007D23D5 /* RoyalVNCObjCDemo.app */, + ); + name = Products; + sourceTree = ""; + }; + E742BD0729018431007D23D5 /* Source */ = { + isa = PBXGroup; + children = ( + E742BD2429018789007D23D5 /* Connection */, + E742BD2329018785007D23D5 /* Credential */, + E742BD222901877F007D23D5 /* Configuration */, + E742BD0829018431007D23D5 /* AppDelegate.h */, + E742BD0929018431007D23D5 /* AppDelegate.m */, + E7B4E3D1290BD1BC00FB35E4 /* MainMenu.xib */, + E742BD1029018432007D23D5 /* main.m */, + E77D15B3290C175D000FDDC4 /* Assets.xcassets */, + E742BD1229018432007D23D5 /* RoyalVNCObjCDemo.entitlements */, + E7AE880B29150EBC001BAE18 /* Debug.xcconfig */, + E7AE880E29151183001BAE18 /* Release.xcconfig */, + ); + path = Source; + sourceTree = ""; + }; + E742BD1829018467007D23D5 /* Frameworks */ = { + isa = PBXGroup; + children = ( + E742BD1929018467007D23D5 /* RoyalVNCKit.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + E742BD222901877F007D23D5 /* Configuration */ = { + isa = PBXGroup; + children = ( + E742BD2C290191EB007D23D5 /* ConfigurationWindowController.h */, + E742BD2D290191EB007D23D5 /* ConfigurationWindowController.m */, + E742BD2A290191D7007D23D5 /* ConfigurationWindow.xib */, + E742BD302901942B007D23D5 /* ConfigurationViewController.h */, + E742BD312901942B007D23D5 /* ConfigurationViewController.m */, + E79EBE272902B5460013A7E4 /* ConfigurationView.xib */, + E79EBE2B2902B5CC0013A7E4 /* EncodingsConfigurationViewController.h */, + E79EBE2C2902B5CC0013A7E4 /* EncodingsConfigurationViewController.m */, + E79EBE1F2902B4C10013A7E4 /* EncodingsConfigurationView.xib */, + ); + path = Configuration; + sourceTree = ""; + }; + E742BD2329018785007D23D5 /* Credential */ = { + isa = PBXGroup; + children = ( + E742BD27290187D4007D23D5 /* CredentialWindowController.h */, + E742BD28290187D4007D23D5 /* CredentialWindowController.m */, + E742BD25290187A1007D23D5 /* CredentialWindow.xib */, + ); + path = Credential; + sourceTree = ""; + }; + E742BD2429018789007D23D5 /* Connection */ = { + isa = PBXGroup; + children = ( + E79EBE362902C3AD0013A7E4 /* ConnectionWindowController.h */, + E79EBE372902C3AD0013A7E4 /* ConnectionWindowController.m */, + E79EBE392902C3CD0013A7E4 /* ConnectionWindowControllerDelegate.h */, + E79EBE312902C3750013A7E4 /* ConnectionWindow.xib */, + E79EBE332902C38C0013A7E4 /* ConnectionViewController.h */, + E79EBE342902C38C0013A7E4 /* ConnectionViewController.m */, + E79EBE3A2902C3E20013A7E4 /* ConnectionViewControllerDelegate.h */, + E79EBE2F2902C36D0013A7E4 /* ConnectionView.xib */, + ); + path = Connection; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + E742BD0429018431007D23D5 /* RoyalVNCObjCDemo */ = { + isa = PBXNativeTarget; + buildConfigurationList = E742BD1529018432007D23D5 /* Build configuration list for PBXNativeTarget "RoyalVNCObjCDemo" */; + buildPhases = ( + E742BD0129018431007D23D5 /* Sources */, + E742BD0229018431007D23D5 /* Frameworks */, + E742BD0329018431007D23D5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = RoyalVNCObjCDemo; + productName = RoyalVNCObjCDemo; + productReference = E742BD0529018431007D23D5 /* RoyalVNCObjCDemo.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + E742BCFD29018431007D23D5 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastUpgradeCheck = 1600; + TargetAttributes = { + E742BD0429018431007D23D5 = { + CreatedOnToolsVersion = 14.0.1; + }; + }; + }; + buildConfigurationList = E742BD0029018431007D23D5 /* Build configuration list for PBXProject "RoyalVNCObjCDemo" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = E742BCFC29018431007D23D5; + productRefGroup = E742BD0629018431007D23D5 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + E742BD0429018431007D23D5 /* RoyalVNCObjCDemo */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + E742BD0329018431007D23D5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + E742BD2B290191D7007D23D5 /* ConfigurationWindow.xib in Resources */, + E79EBE282902B5460013A7E4 /* ConfigurationView.xib in Resources */, + E79EBE302902C36D0013A7E4 /* ConnectionView.xib in Resources */, + E79EBE2E2902B8760013A7E4 /* EncodingsConfigurationView.xib in Resources */, + E79EBE322902C3750013A7E4 /* ConnectionWindow.xib in Resources */, + E742BD26290187A1007D23D5 /* CredentialWindow.xib in Resources */, + E77D15B4290C175D000FDDC4 /* Assets.xcassets in Resources */, + E7B4E3D3290BD1BC00FB35E4 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + E742BD0129018431007D23D5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + E79EBE2D2902B5CC0013A7E4 /* EncodingsConfigurationViewController.m in Sources */, + E79EBE352902C38C0013A7E4 /* ConnectionViewController.m in Sources */, + E742BD2E290191EB007D23D5 /* ConfigurationWindowController.m in Sources */, + E79EBE382902C3AD0013A7E4 /* ConnectionWindowController.m in Sources */, + E742BD29290187D4007D23D5 /* CredentialWindowController.m in Sources */, + E742BD1129018432007D23D5 /* main.m in Sources */, + E742BD0A29018431007D23D5 /* AppDelegate.m in Sources */, + E742BD322901942B007D23D5 /* ConfigurationViewController.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + E7B4E3D1290BD1BC00FB35E4 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + E7B4E3D2290BD1BC00FB35E4 /* Base */, + ); + name = MainMenu.xib; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + E742BD1329018432007D23D5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = E7AE880B29150EBC001BAE18 /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_ENTITLEMENTS = Source/RoyalVNCObjCDemo.entitlements; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + }; + name = Debug; + }; + E742BD1429018432007D23D5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = E7AE880E29151183001BAE18 /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_ENTITLEMENTS = Source/RoyalVNCObjCDemo.entitlements; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + }; + name = Release; + }; + E742BD1629018432007D23D5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = E7AE880B29150EBC001BAE18 /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEAD_CODE_STRIPPING = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_CFBundleDisplayName = "RoyalVNC Demo ObjC"; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; + INFOPLIST_KEY_NSHumanReadableCopyright = "© 2022 Royal Apps GmbH"; + INFOPLIST_KEY_NSMainNibFile = MainMenu; + INFOPLIST_KEY_NSPrincipalClass = NSApplication; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.royalapps.RoyalVNCObjCDemo; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + }; + name = Debug; + }; + E742BD1729018432007D23D5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = E7AE880E29151183001BAE18 /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEAD_CODE_STRIPPING = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_CFBundleDisplayName = "RoyalVNC Demo ObjC"; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; + INFOPLIST_KEY_NSHumanReadableCopyright = "© 2022 Royal Apps GmbH"; + INFOPLIST_KEY_NSMainNibFile = MainMenu; + INFOPLIST_KEY_NSPrincipalClass = NSApplication; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.royalapps.RoyalVNCObjCDemo; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + E742BD0029018431007D23D5 /* Build configuration list for PBXProject "RoyalVNCObjCDemo" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + E742BD1329018432007D23D5 /* Debug */, + E742BD1429018432007D23D5 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + E742BD1529018432007D23D5 /* Build configuration list for PBXNativeTarget "RoyalVNCObjCDemo" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + E742BD1629018432007D23D5 /* Debug */, + E742BD1729018432007D23D5 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCSwiftPackageProductDependency section */ + E79381F42CA72CBF005060BC /* RoyalVNCKit */ = { + isa = XCSwiftPackageProductDependency; + productName = RoyalVNCKit; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = E742BCFD29018431007D23D5 /* Project object */; +} diff --git a/RoyalVNCObjCDemo/RoyalVNCObjCDemo.xcodeproj/xcshareddata/xcschemes/RoyalVNCObjCDemo.xcscheme b/RoyalVNCObjCDemo/RoyalVNCObjCDemo.xcodeproj/xcshareddata/xcschemes/RoyalVNCObjCDemo.xcscheme new file mode 100644 index 0000000..a4b005a --- /dev/null +++ b/RoyalVNCObjCDemo/RoyalVNCObjCDemo.xcodeproj/xcshareddata/xcschemes/RoyalVNCObjCDemo.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/RoyalVNCObjCDemo/Source/AppDelegate.h b/RoyalVNCObjCDemo/Source/AppDelegate.h new file mode 100644 index 0000000..585242d --- /dev/null +++ b/RoyalVNCObjCDemo/Source/AppDelegate.h @@ -0,0 +1,5 @@ +@import Cocoa; + +@interface AppDelegate : NSObject + +@end diff --git a/RoyalVNCObjCDemo/Source/AppDelegate.m b/RoyalVNCObjCDemo/Source/AppDelegate.m new file mode 100644 index 0000000..6ffcd23 --- /dev/null +++ b/RoyalVNCObjCDemo/Source/AppDelegate.m @@ -0,0 +1,52 @@ +#import "AppDelegate.h" + +#import "ConfigurationWindowController.h" + +@import RoyalVNCKit; + +@interface AppDelegate () + +@property (weak) IBOutlet NSMenu* menuConnection; + +@property (weak) IBOutlet NSMenuItem* menuItemConnectionColorDepth8Bit; +@property (weak) IBOutlet NSMenuItem* menuItemConnectionColorDepth16Bit; +@property (weak) IBOutlet NSMenuItem* menuItemConnectionColorDepth24Bit; + +@property (strong) ConfigurationWindowController* configurationWindowController; + +@end + +@implementation AppDelegate + +- (instancetype)init { + self = [super init]; + + if (self) { + self.configurationWindowController = [ConfigurationWindowController new]; + } + + return self; +} + +- (void)applicationDidFinishLaunching:(NSNotification *)aNotification { + [self.configurationWindowController showWindow:self]; +} + +- (void)applicationWillTerminate:(NSNotification *)aNotification { + [self.configurationWindowController saveSettings]; +} + +- (void)menuNeedsUpdate:(NSMenu*)menu { + if (menu != self.menuConnection) { + return; + } + + NSNumber* colorDepthNumber = self.configurationWindowController.colorDepthOfActiveConnection; + VNCColorDepth colorDepth = colorDepthNumber != nil ? (VNCColorDepth)colorDepthNumber.unsignedCharValue : -1; + + self.menuItemConnectionColorDepth8Bit.state = colorDepth == VNCColorDepthDepth8Bit ? NSControlStateValueOn : NSControlStateValueOff; + self.menuItemConnectionColorDepth16Bit.state = colorDepth == VNCColorDepthDepth16Bit ? NSControlStateValueOn : NSControlStateValueOff; + self.menuItemConnectionColorDepth24Bit.state = colorDepth == VNCColorDepthDepth24Bit ? NSControlStateValueOn : NSControlStateValueOff; +} + +@end diff --git a/RoyalVNCObjCDemo/Source/Base.lproj/MainMenu.xib b/RoyalVNCObjCDemo/Source/Base.lproj/MainMenu.xib new file mode 100644 index 0000000..2a17401 --- /dev/null +++ b/RoyalVNCObjCDemo/Source/Base.lproj/MainMenu.xib @@ -0,0 +1,681 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Default + + + + + + + Left to Right + + + + + + + Right to Left + + + + + + + + + + + Default + + + + + + + Left to Right + + + + + + + Right to Left + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/RoyalVNCObjCDemo/Source/Configuration/ConfigurationViewController.h b/RoyalVNCObjCDemo/Source/Configuration/ConfigurationViewController.h new file mode 100644 index 0000000..489012c --- /dev/null +++ b/RoyalVNCObjCDemo/Source/Configuration/ConfigurationViewController.h @@ -0,0 +1,14 @@ +@import Cocoa; +@import RoyalVNCKit; + +NS_ASSUME_NONNULL_BEGIN + +@interface ConfigurationViewController : NSViewController + +@property (strong) VNCConnectionSettings* settings; + +- (instancetype)initWithSettings:(VNCConnectionSettings*)settings; + +@end + +NS_ASSUME_NONNULL_END diff --git a/RoyalVNCObjCDemo/Source/Configuration/ConfigurationViewController.m b/RoyalVNCObjCDemo/Source/Configuration/ConfigurationViewController.m new file mode 100644 index 0000000..010309d --- /dev/null +++ b/RoyalVNCObjCDemo/Source/Configuration/ConfigurationViewController.m @@ -0,0 +1,179 @@ +#import "ConfigurationViewController.h" +#import "EncodingsConfigurationViewController.h" + +@interface ConfigurationViewController () + +@property (strong) VNCConnectionSettings* initialSettings; + +@property (strong) EncodingsConfigurationViewController* encodingsConfigurationViewController; +@property BOOL didLoad; + +@property (weak) IBOutlet NSTextField* textFieldHostname; +@property (weak) IBOutlet NSTextField* textFieldPort; + +@property (weak) IBOutlet NSButton* checkBoxShared; +@property (weak) IBOutlet NSButton* checkBoxClipboardRedirection; +@property (weak) IBOutlet NSButton* checkBoxScaling; +@property (weak) IBOutlet NSButton* checkBoxUseDisplayLink; +@property (weak) IBOutlet NSButton* checkBoxDebugLogging; + +@property (weak) IBOutlet NSPopUpButton* popupButtonInputMode; +@property (weak) IBOutlet NSPopUpButton* popupButtonColorDepth; +@property (weak) IBOutlet NSView* placeholderViewEncodings; + +@end + +@implementation ConfigurationViewController + +- (instancetype)initWithSettings:(VNCConnectionSettings *)settings { + self = [super initWithNibName:@"ConfigurationView" + bundle:NSBundle.mainBundle]; + + if (self) { + self.initialSettings = settings; + + NSArray* supportedFrameEncodings = VNCFrameEncodingTypeUtils.defaultFrameEncodings; + + self.encodingsConfigurationViewController = [[EncodingsConfigurationViewController alloc] initWithSupportedFrameEncodings:supportedFrameEncodings]; + } + + return self; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + if (self.didLoad) { + return; + } + + self.didLoad = YES; + + NSView* encodingsConfigurationView = self.encodingsConfigurationViewController.view; + encodingsConfigurationView.frame = self.placeholderViewEncodings.bounds; + + [self.placeholderViewEncodings addSubview:encodingsConfigurationView]; + + self.settings = self.initialSettings; +} + +- (BOOL)becomeFirstResponder { + return [self.textFieldHostname becomeFirstResponder]; +} + +- (VNCConnectionSettings *)settings { + return [[VNCConnectionSettings alloc] initWithIsDebugLoggingEnabled:self.isDebugLoggingEnabled + hostname:self.hostname + port:self.port + isShared:self.isShared + isScalingEnabled:self.isScalingEnabled + useDisplayLink:self.useDisplayLink + inputMode:self.inputMode + isClipboardRedirectionEnabled:self.isClipboardRedirectionEnabled + colorDepth:self.colorDepth + frameEncodings:self.frameEncodings]; +} + +- (void)setSettings:(VNCConnectionSettings *)settings { + self.isDebugLoggingEnabled = settings.isDebugLoggingEnabled; + self.hostname = settings.hostname; + self.port = settings.port; + self.isShared = settings.isShared; + self.isScalingEnabled = settings.isScalingEnabled; + self.useDisplayLink = settings.useDisplayLink; + self.inputMode = settings.inputMode; + self.isClipboardRedirectionEnabled = settings.isClipboardRedirectionEnabled; + self.colorDepth = settings.colorDepth; + self.frameEncodings = settings.frameEncodings; +} + +- (BOOL)isDebugLoggingEnabled { + return self.checkBoxDebugLogging.state == NSControlStateValueOn; +} +- (void)setIsDebugLoggingEnabled:(BOOL)isDebugLoggingEnabled { + self.checkBoxDebugLogging.state = isDebugLoggingEnabled ? NSControlStateValueOn : NSControlStateValueOff; +} + +- (NSString*)hostname { + return self.textFieldHostname.stringValue; +} +- (void)setHostname:(NSString*)hostname { + self.textFieldHostname.stringValue = hostname; +} + +- (uint16_t)port { + return (uint16_t)self.textFieldPort.intValue; +} +- (void)setPort:(uint16_t)port { + self.textFieldPort.intValue = port; +} + +- (BOOL)isShared { + return self.checkBoxShared.state == NSControlStateValueOn; +} +- (void)setIsShared:(BOOL)isShared { + self.checkBoxShared.state = isShared ? NSControlStateValueOn : NSControlStateValueOff; +} + +- (BOOL)isScalingEnabled { + return self.checkBoxScaling.state == NSControlStateValueOn; +} +- (void)setIsScalingEnabled:(BOOL)isScalingEnabled { + self.checkBoxScaling.state = isScalingEnabled ? NSControlStateValueOn : NSControlStateValueOff; +} + +- (BOOL)useDisplayLink { + return self.checkBoxUseDisplayLink.state == NSControlStateValueOn; +} +- (void)setUseDisplayLink:(BOOL)useDisplayLink { + self.checkBoxUseDisplayLink.state = useDisplayLink ? NSControlStateValueOn : NSControlStateValueOff; +} + +- (VNCInputMode)inputMode { + return (VNCInputMode)self.popupButtonInputMode.indexOfSelectedItem; +} +- (void)setInputMode:(VNCInputMode)inputMode { + [self.popupButtonInputMode selectItemAtIndex:inputMode]; +} + +- (BOOL)isClipboardRedirectionEnabled { + return self.checkBoxClipboardRedirection.state == NSControlStateValueOn; +} +- (void)setIsClipboardRedirectionEnabled:(BOOL)isClipboardRedirectionEnabled { + self.checkBoxClipboardRedirection.state = isClipboardRedirectionEnabled ? NSControlStateValueOn : NSControlStateValueOff; +} + +- (VNCColorDepth)colorDepth { + switch (self.popupButtonColorDepth.indexOfSelectedItem) { + case 0: + return VNCColorDepthDepth8Bit; + case 1: + return VNCColorDepthDepth16Bit; + case 2: + return VNCColorDepthDepth24Bit; + default: + return VNCColorDepthDepth24Bit; + } +} +- (void)setColorDepth:(VNCColorDepth)colorDepth { + switch (colorDepth) { + case VNCColorDepthDepth8Bit: + [self.popupButtonColorDepth selectItemAtIndex:0]; + break; + case VNCColorDepthDepth16Bit: + [self.popupButtonColorDepth selectItemAtIndex:1]; + break; + case VNCColorDepthDepth24Bit: + [self.popupButtonColorDepth selectItemAtIndex:2]; + break; + } +} + +- (NSArray*)frameEncodings { + return self.encodingsConfigurationViewController.frameEncodings; +} +- (void)setFrameEncodings:(NSArray*)frameEncodings { + self.encodingsConfigurationViewController.frameEncodings = frameEncodings; +} + +@end diff --git a/RoyalVNCObjCDemo/Source/Configuration/ConfigurationWindowController.h b/RoyalVNCObjCDemo/Source/Configuration/ConfigurationWindowController.h new file mode 100644 index 0000000..0f7b21d --- /dev/null +++ b/RoyalVNCObjCDemo/Source/Configuration/ConfigurationWindowController.h @@ -0,0 +1,13 @@ +@import Cocoa; + +NS_ASSUME_NONNULL_BEGIN + +@interface ConfigurationWindowController : NSWindowController + +@property (readonly, nullable) NSNumber* colorDepthOfActiveConnection; + +- (void)saveSettings; + +@end + +NS_ASSUME_NONNULL_END diff --git a/RoyalVNCObjCDemo/Source/Configuration/ConfigurationWindowController.m b/RoyalVNCObjCDemo/Source/Configuration/ConfigurationWindowController.m new file mode 100644 index 0000000..1243adf --- /dev/null +++ b/RoyalVNCObjCDemo/Source/Configuration/ConfigurationWindowController.m @@ -0,0 +1,179 @@ +#import "ConfigurationWindowController.h" +#import "ConfigurationViewController.h" + +#import "ConnectionWindowController.h" + +@import RoyalVNCKit; + +@interface ConfigurationWindowController () + +@property BOOL didLoad; + +@property (strong) ConfigurationViewController* configurationViewController; +@property (strong) NSMutableArray* connectionWindowControllers; + +@property (weak) IBOutlet NSView* viewPlaceholderConfiguration; +@property (weak) IBOutlet NSButton* buttonConnect; + +@end + +@implementation ConfigurationWindowController + +- (NSNibName)windowNibName { + return @"ConfigurationWindow"; +} + +- (instancetype)init { + self = [super initWithWindow:nil]; + + if (self) { + VNCConnectionSettings* settings = [VNCConnectionSettings fromUserDefaults]; + + self.configurationViewController = [[ConfigurationViewController alloc] initWithSettings:settings]; + self.connectionWindowControllers = [NSMutableArray array]; + } + + return self; +} + +- (void)windowDidLoad { + [super windowDidLoad]; + + if (self.didLoad) { + return; + } + + self.didLoad = YES; + + [self configureUI]; +} + +- (void)configureUI { + NSView* configView = self.configurationViewController.view; + configView.frame = self.viewPlaceholderConfiguration.bounds; + configView.autoresizingMask = NSViewMinXMargin | NSViewMaxXMargin | NSViewMinYMargin | NSViewMaxYMargin | NSViewWidthSizable | NSViewHeightSizable; + + [self.viewPlaceholderConfiguration addSubview:configView]; + + [self.window recalculateKeyViewLoop]; + [self.window makeFirstResponder:self.configurationViewController]; +} + +- (IBAction)buttonConnect_action:(id)sender { + [self connect]; +} + +- (void)saveSettings { + [self.configurationViewController.settings saveToUserDefaults]; +} + +- (NSNumber*)colorDepthOfActiveConnection { + VNCConnection* activeConnection = self.activeConnection; + + if (!activeConnection) { + return nil; + } + + VNCFramebuffer* framebuffer = activeConnection.framebuffer; + + if (!framebuffer) { + return nil; + } + + VNCColorDepth colorDepth = framebuffer.colorDepth; + + return @(colorDepth); +} + +- (void)connect { + NSWindow* window = self.window; + + if (!window) { + return; + } + + VNCConnectionSettings* settings = self.configurationViewController.settings; + [settings saveToUserDefaults]; + + if ([VNCInputModeUtils inputModeRequiresAccessibilityPermissions:settings.inputMode] && + !VNCAccessibilityUtils.hasAccessibilityPermissions) { + // Requires accessibility permissions but don't have them right now so ask user + + NSAlert* alert = [NSAlert new]; + alert.messageText = @"Accessibility Permissions"; + alert.informativeText = @"Accessibility Permissions are required when the input mode is set to \"Forward all keyboard shortcuts and hot keys\". To continue, please open System Settings and grant the app accessibility permissions."; + + [alert addButtonWithTitle:@"Open System Settings"]; + [alert addButtonWithTitle:@"Continue without permissions"]; + [alert addButtonWithTitle:@"Cancel"]; + + __weak ConfigurationWindowController* weakSelf = self; + + [alert beginSheetModalForWindow:window completionHandler:^(NSModalResponse response) { + switch (response) { + case NSAlertFirstButtonReturn: + [VNCAccessibilityUtils openAccessibilityPermissionsPreferencePane]; + + break; + case NSAlertSecondButtonReturn: + [weakSelf connectWithSettings:settings]; + + break; + default: + return; + } + }]; + } else { + [self connectWithSettings:settings]; + } +} + +- (void)connectWithSettings:(VNCConnectionSettings*)settings { + ConnectionWindowController* connectionWindowController = [[ConnectionWindowController alloc] initWithSettings:settings]; + connectionWindowController.delegate = self; + + [self.connectionWindowControllers addObject:connectionWindowController]; + + [connectionWindowController showWindow:self]; + [connectionWindowController connect]; +} + +- (nullable VNCConnection*)activeConnection { + ConnectionWindowController* activeConnectionWindowController = self.activeConnectionWindowController; + + if (!activeConnectionWindowController) { + return nil; + } + + return activeConnectionWindowController.connection; +} + +- (nullable ConnectionWindowController*)activeConnectionWindowController { + NSWindow* keyWindow = NSApplication.sharedApplication.keyWindow; + + if (!keyWindow) { + return nil; + } + + ConnectionWindowController* keyConnectionWindowController = nil; + + for (ConnectionWindowController* connectionWindowController in self.connectionWindowControllers) { + if (connectionWindowController.window == keyWindow) { + keyConnectionWindowController = connectionWindowController; + + break; + } + } + + return keyConnectionWindowController; +} + +#pragma mark - ConnectionWindowControllerDelegate + +- (void)connectionWindowControllerDidClose:(ConnectionWindowController*)connectionWindowController { + connectionWindowController.delegate = nil; + + [self.connectionWindowControllers removeObject:connectionWindowController]; +} + +@end diff --git a/RoyalVNCObjCDemo/Source/Configuration/EncodingsConfigurationViewController.h b/RoyalVNCObjCDemo/Source/Configuration/EncodingsConfigurationViewController.h new file mode 100644 index 0000000..516db46 --- /dev/null +++ b/RoyalVNCObjCDemo/Source/Configuration/EncodingsConfigurationViewController.h @@ -0,0 +1,15 @@ +@import Cocoa; +@import RoyalVNCKit; + +NS_ASSUME_NONNULL_BEGIN + +@interface EncodingsConfigurationViewController : NSViewController + +@property (readonly, copy) NSArray* supportedFrameEncodings; +@property (copy) NSArray* frameEncodings; + +- (instancetype)initWithSupportedFrameEncodings:(NSArray*)supportedFrameEncodings; + +@end + +NS_ASSUME_NONNULL_END diff --git a/RoyalVNCObjCDemo/Source/Configuration/EncodingsConfigurationViewController.m b/RoyalVNCObjCDemo/Source/Configuration/EncodingsConfigurationViewController.m new file mode 100644 index 0000000..4115c8d --- /dev/null +++ b/RoyalVNCObjCDemo/Source/Configuration/EncodingsConfigurationViewController.m @@ -0,0 +1,211 @@ +#import "EncodingsConfigurationViewController.h" + +@interface EncodingsConfigurationViewController () + +@property (readwrite, strong) NSMutableArray* _frameEncodings; + +@property (readwrite, copy) NSArray* supportedFrameEncodings; +@property BOOL didLoad; + +@property (weak) IBOutlet NSTableView* tableView; + +@end + +@implementation EncodingsConfigurationViewController + +- (instancetype)initWithSupportedFrameEncodings:(NSArray*)supportedFrameEncodings { + self = [super initWithNibName:@"EncodingsConfigurationView" bundle:NSBundle.mainBundle]; + + if (self) { + self.supportedFrameEncodings = supportedFrameEncodings; + self.frameEncodings = supportedFrameEncodings; + } + + return self; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + if (self.didLoad) { + return; + } + + self.didLoad = YES; + + self.tableView.dataSource = self; + self.tableView.delegate = self; +} + +- (NSArray*)frameEncodings { + return [self._frameEncodings copy]; +} + +- (void)setFrameEncodings:(NSArray*)frameEncodings { + NSMutableArray* copiedFrameEncodings = [NSMutableArray array]; + + for (NSNumber* encodingNumber in frameEncodings) { + [copiedFrameEncodings addObject:encodingNumber]; + } + + self._frameEncodings = copiedFrameEncodings; + + [self.tableView reloadData]; +} + +- (NSArray*)orderedFrameEncodings { + return [self orderedFrameEncodings:self.supportedFrameEncodings + order:self._frameEncodings]; +} + +- (IBAction)checkBoxEncoding_action:(NSButton*)sender { + NSInteger row = [self.tableView rowForView:sender]; + + if (row == NSNotFound || + row < 0) { + return; + } + + VNCFrameEncodingType encoding = (VNCFrameEncodingType)self.orderedFrameEncodings[row].intValue; + BOOL isEnabled = sender.state == NSControlStateValueOn; + + [self setEncoding:encoding + index:row + enabled:isEnabled]; +} + +- (IBAction)buttonMoveUp_action:(NSButton*)sender { + NSInteger row = self.tableView.selectedRow; + + if (row == NSNotFound || + row < 0) { + return; + } + + [self moveEncodingAtIndex:row + up:YES]; +} + +- (IBAction)buttonMoveDown_action:(NSButton*)sender { + NSInteger row = self.tableView.selectedRow; + + if (row == NSNotFound || + row < 0) { + return; + } + + [self moveEncodingAtIndex:row + up:NO]; +} + +- (NSArray*)orderedFrameEncodings:(NSArray*)encodings + order:(NSArray*)order { + NSMutableArray* filteredOrder = [NSMutableArray array]; + + for (NSNumber* encodingNumber in order) { + VNCFrameEncodingType encoding = (VNCFrameEncodingType)encodingNumber.intValue; + + if ([self isEncodingEnabled:encoding in:encodings]) { + [filteredOrder addObject:encodingNumber]; + } + } + + for (NSNumber* encodingNumber in encodings) { + VNCFrameEncodingType encoding = (VNCFrameEncodingType)encodingNumber.intValue; + + if (![self isEncodingEnabled:encoding in:order]) { + [filteredOrder addObject:encodingNumber]; + } + } + + return [filteredOrder copy]; +} + +- (void)setEncoding:(VNCFrameEncodingType)encoding + index:(NSInteger)index + enabled:(BOOL)isEnabled { + if (isEnabled) { + if (index < self._frameEncodings.count) { + [self._frameEncodings insertObject:@(encoding) atIndex:index]; + } else { + [self._frameEncodings addObject:@(encoding)]; + } + } else { + [self._frameEncodings removeObjectAtIndex:index]; + } + + [self.tableView reloadData]; +} + +- (void)moveEncodingAtIndex:(NSInteger)index + up:(BOOL)up { + if (index < 0 || + index >= self._frameEncodings.count) { + return; + } + + NSInteger newIndex = up + ? MAX(index - 1, 0) + : MIN(index + 1, self._frameEncodings.count - 1); + + VNCFrameEncodingType encoding = (VNCFrameEncodingType)self._frameEncodings[index].integerValue; + + [self._frameEncodings removeObjectAtIndex:index]; + [self._frameEncodings insertObject:@(encoding) atIndex:newIndex]; + + [self.tableView reloadData]; + + [self.tableView selectRowIndexes:[NSIndexSet indexSetWithIndex:newIndex] + byExtendingSelection:NO]; +} + +- (BOOL)isEncodingEnabled:(VNCFrameEncodingType)encoding + in:(NSArray*)encodings { + for (NSNumber* encodingNumber in encodings) { + VNCFrameEncodingType currentEncoding = (VNCFrameEncodingType)encodingNumber.intValue; + + if (currentEncoding == encoding) { + return YES; + } + } + + return NO; +} + +- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView { + return self.orderedFrameEncodings.count; +} + +- (NSView *)tableView:(NSTableView *)tableView + viewForTableColumn:(NSTableColumn *)tableColumn + row:(NSInteger)row { + VNCFrameEncodingType encoding = (VNCFrameEncodingType)self.orderedFrameEncodings[row].intValue; + + NSTableCellView* cellView = [tableView makeViewWithIdentifier:@"EncodingCell" owner:nil]; + NSButton* checkBox = [cellView viewWithTag:450]; + + if (!cellView || + !checkBox) { + return nil; + } + + NSString* title = [VNCFrameEncodingTypeUtils descriptionForFrameEncoding:encoding]; + + BOOL isEnabled = [self isEncodingEnabled:encoding + in:self._frameEncodings]; + + checkBox.title = title; + checkBox.state = isEnabled ? NSControlStateValueOn : NSControlStateValueOff; + + checkBox.target = self; + checkBox.action = @selector(checkBoxEncoding_action:); + + return cellView; +} + +- (BOOL)tableView:(NSTableView *)tableView + isGroupRow:(NSInteger)row { + return NO; +} + +@end diff --git a/RoyalVNCObjCDemo/Source/Connection/ConnectionViewController.h b/RoyalVNCObjCDemo/Source/Connection/ConnectionViewController.h new file mode 100644 index 0000000..c116824 --- /dev/null +++ b/RoyalVNCObjCDemo/Source/Connection/ConnectionViewController.h @@ -0,0 +1,20 @@ +@import Cocoa; +@import RoyalVNCKit; + +#import "ConnectionViewControllerDelegate.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface ConnectionViewController : NSViewController + +@property (readonly, nullable) VNCConnection* connection; +@property (readwrite, weak) id delegate; + +- (instancetype)initWithSettings:(VNCConnectionSettings*)settings; + +- (void)connect; +- (void)disconnect; + +@end + +NS_ASSUME_NONNULL_END diff --git a/RoyalVNCObjCDemo/Source/Connection/ConnectionViewController.m b/RoyalVNCObjCDemo/Source/Connection/ConnectionViewController.m new file mode 100644 index 0000000..e8b175d --- /dev/null +++ b/RoyalVNCObjCDemo/Source/Connection/ConnectionViewController.m @@ -0,0 +1,329 @@ +#import "ConnectionViewController.h" +#import "CredentialWindowController.h" + +@interface ConnectionViewController () + +@property (weak) IBOutlet NSProgressIndicator* progressIndicator; +@property (weak) IBOutlet NSTextField* textFieldStatus; +@property (weak) IBOutlet VNCScrollView* framebufferScrollView; + +@property (strong) VNCConnection* connection; +@property (strong, nullable) VNCCAFramebufferView* framebufferView; +@property (strong, nullable) CredentialWindowController* credentialWindowController; + +@end + +@implementation ConnectionViewController + +- (instancetype)initWithSettings:(VNCConnectionSettings*)settings { + self = [super initWithNibName:@"ConnectionView" + bundle:NSBundle.mainBundle]; + + if (self) { + self.connection = [[VNCConnection alloc] initWithSettings:settings]; + self.connection.delegate = self; + } + + return self; +} + +- (void)dealloc { + self.connection.delegate = nil; +} + +- (void)connect { + [self.connection connect]; +} + +- (void)disconnect { + if (self.connection.connectionState.status == VNCConnectionStatusDisconnected) { + return; + } + + [self.connection disconnect]; +} + +- (void)showProgressWithStatusText:(NSString*)statusText { + [self.progressIndicator startAnimation:nil]; + self.progressIndicator.hidden = NO; + + self.textFieldStatus.stringValue = statusText; + self.textFieldStatus.hidden = NO; +} + +- (void)hideProgress { + [self.progressIndicator stopAnimation:nil]; + self.progressIndicator.hidden = YES; + + self.textFieldStatus.hidden = YES; +} + +- (void)createFramebufferViewForConnection:(VNCConnection*)connection + framebuffer:(VNCFramebuffer*)framebuffer { + [self createFramebufferViewForConnection:connection + framebuffer:framebuffer + inScrollView:self.framebufferScrollView + makeFirstResponder:YES]; +} + +- (id)createFramebufferViewForConnection:(VNCConnection*)connection + framebuffer:(VNCFramebuffer*)framebuffer + inScrollView:(VNCScrollView*)scrollView + makeFirstResponder:(BOOL)makeFirstResponder { + [self destroyFramebufferView]; + + BOOL isScalingEnabled = connection.settings.isScalingEnabled; + + CGSize viewSize = isScalingEnabled + ? scrollView.bounds.size + : framebuffer.size; + + CGRect rect = CGRectMake(0, 0, + viewSize.width, viewSize.height); + + VNCCAFramebufferView* view = [[VNCCAFramebufferView alloc] initWithFrame:rect + framebuffer:framebuffer + connection:connection]; + + if (isScalingEnabled) { + view.autoresizingMask = NSViewMinXMargin | NSViewMaxXMargin | NSViewMinYMargin | NSViewMaxYMargin | NSViewWidthSizable | NSViewHeightSizable; + + scrollView.hasVerticalScroller = NO; + scrollView.hasHorizontalScroller = NO; + } else { + scrollView.hasVerticalScroller = YES; + scrollView.hasHorizontalScroller = YES; + } + + scrollView.documentView = view; + + self.framebufferView = view; + + if (makeFirstResponder) { + [view.window makeFirstResponder:view]; + } + + return view; +} + +- (void)destroyFramebufferView { + if (!self.framebufferView) { + return; + } + + [(NSView*)self.framebufferView removeFromSuperview]; + self.framebufferView = nil; +} + +- (void)handleConnectionStateDidChange:(VNCConnectionState*)connectionState { + NSString* statusText = nil; + + BOOL didCloseConnection = NO; + + switch (connectionState.status) { + case VNCConnectionStatusConnecting: + statusText = @"Connecting…"; + break; + case VNCConnectionStatusDisconnecting: + statusText = @"Disconnecting…"; + break; + case VNCConnectionStatusConnected: + statusText = nil; + break; + case VNCConnectionStatusDisconnected: + statusText = nil; + didCloseConnection = YES; + break; + } + + if (statusText) { + [self showProgressWithStatusText:statusText]; + } else { + [self hideProgress]; + } + + if (!didCloseConnection) { + return; + } + + [self handleConnectionDidCloseWithError:connectionState.error]; +} + +- (void)handleConnectionDidCloseWithError:(nullable NSError*)error { + self.connection.delegate = nil; + + [self destroyFramebufferView]; + + [self presentError:error completionHandler:^{ + [self.delegate connectionViewControllerDidDisconnect:self]; + }]; +} + +- (void)presentError:(nullable NSError*)error + completionHandler:(void (^)(void))completionHandler { + if (!error) { + completionHandler(); + + return; + } + + BOOL shouldDisplayError = [VNCErrorUtils shouldDisplayErrorToUser:error]; + + if (!shouldDisplayError) { + completionHandler(); + + return; + } + + NSString* errorText = error.localizedDescription; + + NSAlert* alert = [NSAlert new]; + alert.alertStyle = NSAlertStyleWarning; + alert.messageText = @"Disconnected with Error"; + alert.informativeText = errorText; + + [alert addButtonWithTitle:@"OK"]; + + if (self.view.window) { + [alert beginSheetModalForWindow:self.view.window completionHandler:^(NSModalResponse returnCode) { + completionHandler(); + }]; + } else { + [alert runModal]; + + completionHandler(); + } +} + +- (void)credentialForAuthenticationType:(VNCAuthenticationType)authenticationType + completionHandler:(void (^)(__nullable id credential))completionHandler { + NSWindow* window = self.view.window; + + if (!window) { + completionHandler(nil); + + return; + } + + VNCConnectionSettings* settings = self.connection.settings; + + NSString* cachedUsername = settings.cachedUsername; + NSString* cachedPassword = settings.cachedPassword; + + CredentialWindowController* windowController = [[CredentialWindowController alloc] initWithAuthenticationType:authenticationType + previousUsername:cachedUsername + previousPassword:cachedPassword]; + + self.credentialWindowController = windowController; + + __weak ConnectionViewController* weakSelf = self; + + [windowController beginSheetForParentWindow:window + completionHandler:^(id _Nullable credential) { + weakSelf.credentialWindowController = nil; + + if (credential) { + if ([(NSObject*)credential isKindOfClass:VNCUsernamePasswordCredential.class]) { + VNCUsernamePasswordCredential* userPassCred = (VNCUsernamePasswordCredential*)credential; + + if (![userPassCred.username isEqualToString:cachedUsername]) { + settings.cachedUsername = userPassCred.username; + } + + if (![userPassCred.password isEqualToString:cachedPassword]) { + settings.cachedPassword = userPassCred.password; + } + } else if ([(NSObject*)credential isKindOfClass:VNCPasswordCredential.class]) { + VNCPasswordCredential* passCred = (VNCPasswordCredential*)credential; + + if (![passCred.password isEqualToString:cachedPassword]) { + settings.cachedPassword = passCred.password; + } + } + } + + completionHandler(credential); + }]; +} + +- (IBAction)setColorDepth8Bit:(id)sender { + [self.connection updateColorDepth:VNCColorDepthDepth8Bit]; +} + +- (IBAction)setColorDepth16Bit:(id)sender { + [self.connection updateColorDepth:VNCColorDepthDepth16Bit]; +} + +- (IBAction)setColorDepth24Bit:(id)sender { + [self.connection updateColorDepth:VNCColorDepthDepth24Bit]; +} + +#pragma mark VNCConnectionDelegate + +- (void)connection:(VNCConnection*)connection + stateDidChange:(VNCConnectionState*)connectionState { + __weak ConnectionViewController* weakSelf = self; + + dispatch_async(dispatch_get_main_queue(), ^{ + [weakSelf handleConnectionStateDidChange:connectionState]; + }); +} + +- (void)connection:(VNCConnection*)connection + credentialFor:(enum VNCAuthenticationType)authenticationType + completion:(void (^)(id _Nullable))completion { + __weak ConnectionViewController* weakSelf = self; + + dispatch_async(dispatch_get_main_queue(), ^{ + if (!weakSelf) { + completion(nil); + + return; + } + + [weakSelf credentialForAuthenticationType:authenticationType + completionHandler:completion]; + }); +} + +- (void)connection:(VNCConnection*)connection didCreateFramebuffer:(VNCFramebuffer*)framebuffer { + __weak ConnectionViewController* weakSelf = self; + + dispatch_async(dispatch_get_main_queue(), ^{ + if (!weakSelf) { + return; + } + + [weakSelf createFramebufferViewForConnection:connection + framebuffer:framebuffer]; + }); +} + +- (void)connection:(VNCConnection*)connection didResizeFramebuffer:(VNCFramebuffer*)framebuffer { + __weak ConnectionViewController* weakSelf = self; + + dispatch_async(dispatch_get_main_queue(), ^{ + if (!weakSelf) { + return; + } + + [weakSelf createFramebufferViewForConnection:connection + framebuffer:framebuffer]; + }); +} + +- (void)connection:(VNCConnection*)connection + framebuffer:(VNCFramebuffer*)framebuffer + didUpdateRegion:(CGRect)updatedRegion { + [self.framebufferView connection:connection + framebuffer:framebuffer + didUpdateRegion:updatedRegion]; +} + +- (void)connection:(VNCConnection*)connection + didUpdateCursor:(VNCCursor*)cursor { + [self.framebufferView connection:connection + didUpdateCursor:cursor]; +} + +@end diff --git a/RoyalVNCObjCDemo/Source/Connection/ConnectionViewControllerDelegate.h b/RoyalVNCObjCDemo/Source/Connection/ConnectionViewControllerDelegate.h new file mode 100644 index 0000000..05d4ba2 --- /dev/null +++ b/RoyalVNCObjCDemo/Source/Connection/ConnectionViewControllerDelegate.h @@ -0,0 +1,12 @@ +#ifndef ConnectionViewControllerDelegate_h +#define ConnectionViewControllerDelegate_h + +@class ConnectionViewController; + +@protocol ConnectionViewControllerDelegate + +- (void)connectionViewControllerDidDisconnect:(ConnectionViewController*)connectionViewController; + +@end + +#endif /* ConnectionViewControllerDelegate_h */ diff --git a/RoyalVNCObjCDemo/Source/Connection/ConnectionWindowController.h b/RoyalVNCObjCDemo/Source/Connection/ConnectionWindowController.h new file mode 100644 index 0000000..846e2ed --- /dev/null +++ b/RoyalVNCObjCDemo/Source/Connection/ConnectionWindowController.h @@ -0,0 +1,21 @@ +@import Cocoa; +@import RoyalVNCKit; + +#import "ConnectionWindowControllerDelegate.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface ConnectionWindowController : NSWindowController + +@property (readonly, strong) VNCConnectionSettings* settings; +@property (readonly, nullable) VNCConnection* connection; +@property (readwrite, weak) id delegate; + +- (instancetype)initWithSettings:(VNCConnectionSettings*)settings; + +- (void)connect; +- (void)disconnect; + +@end + +NS_ASSUME_NONNULL_END diff --git a/RoyalVNCObjCDemo/Source/Connection/ConnectionWindowController.m b/RoyalVNCObjCDemo/Source/Connection/ConnectionWindowController.m new file mode 100644 index 0000000..d65493a --- /dev/null +++ b/RoyalVNCObjCDemo/Source/Connection/ConnectionWindowController.m @@ -0,0 +1,87 @@ +#import "ConnectionWindowController.h" + +#import "ConnectionViewController.h" +#import "ConnectionViewControllerDelegate.h" + +@interface ConnectionWindowController () + +@property (readwrite, strong) VNCConnectionSettings* settings; +@property (strong) ConnectionViewController* connectionViewController; + +@property BOOL didLoad; + +@end + +@implementation ConnectionWindowController + +- (NSNibName)windowNibName { + return @"ConnectionWindow"; +} + +- (instancetype)initWithSettings:(VNCConnectionSettings *)settings { + self = [super initWithWindow:nil]; + + if (self) { + self.settings = settings; + + self.connectionViewController = [[ConnectionViewController alloc] initWithSettings:settings]; + self.connectionViewController.delegate = self; + } + + return self; +} + +- (void)dealloc { + self.delegate = nil; +} + +- (void)windowDidLoad { + [super windowDidLoad]; + + if (self.didLoad) { + return; + } + + self.didLoad = YES; + + if (!self.window || + !self.window.contentView) { + return; + } + + self.window.title = [NSString stringWithFormat:@"Connection to %@", self.settings.hostname]; + + NSView* connectionView = self.connectionViewController.view; + connectionView.frame = self.window.contentView.bounds; + connectionView.autoresizingMask = NSViewMinXMargin | NSViewMaxXMargin | NSViewMinYMargin | NSViewMaxYMargin | NSViewWidthSizable | NSViewHeightSizable; + + [self.window.contentView addSubview:connectionView]; +} + +- (void)connect { + [self.connectionViewController connect]; +} + +- (void)disconnect { + [self.connectionViewController disconnect]; +} + +- (BOOL)windowShouldClose:(NSWindow*)sender { + [self disconnect]; + + return NO; +} + +- (VNCConnection*)connection { + return self.connectionViewController.connection; +} + +- (void)connectionViewControllerDidDisconnect:(ConnectionViewController*)connectionViewController { + self.connectionViewController.delegate = nil; + + [self close]; + + [self.delegate connectionWindowControllerDidClose:self]; +} + +@end diff --git a/RoyalVNCObjCDemo/Source/Connection/ConnectionWindowControllerDelegate.h b/RoyalVNCObjCDemo/Source/Connection/ConnectionWindowControllerDelegate.h new file mode 100644 index 0000000..c562f9a --- /dev/null +++ b/RoyalVNCObjCDemo/Source/Connection/ConnectionWindowControllerDelegate.h @@ -0,0 +1,12 @@ +#ifndef ConnectionWindowControllerDelegate_h +#define ConnectionWindowControllerDelegate_h + +@class ConnectionWindowController; + +@protocol ConnectionWindowControllerDelegate + +- (void)connectionWindowControllerDidClose:(ConnectionWindowController*)connectionWindowController; + +@end + +#endif /* ConnectionWindowControllerDelegate_h */ diff --git a/RoyalVNCObjCDemo/Source/Credential/CredentialWindowController.h b/RoyalVNCObjCDemo/Source/Credential/CredentialWindowController.h new file mode 100644 index 0000000..e89e8db --- /dev/null +++ b/RoyalVNCObjCDemo/Source/Credential/CredentialWindowController.h @@ -0,0 +1,21 @@ +@import Cocoa; +@import RoyalVNCKit; + +NS_ASSUME_NONNULL_BEGIN + +@interface CredentialWindowController : NSWindowController + +@property (readonly) VNCAuthenticationType authenticationType; +@property (readonly, strong) NSString* previousUsername; +@property (readonly, strong) NSString* previousPassword; + +- (instancetype)initWithAuthenticationType:(VNCAuthenticationType)authenticationType + previousUsername:(NSString*)previousUsername + previousPassword:(NSString*)previousPassword; + +- (void)beginSheetForParentWindow:(NSWindow*)parentWindow + completionHandler:(void (^)(__nullable id credential))completionHandler; + +@end + +NS_ASSUME_NONNULL_END diff --git a/RoyalVNCObjCDemo/Source/Credential/CredentialWindowController.m b/RoyalVNCObjCDemo/Source/Credential/CredentialWindowController.m new file mode 100644 index 0000000..18ace81 --- /dev/null +++ b/RoyalVNCObjCDemo/Source/Credential/CredentialWindowController.m @@ -0,0 +1,143 @@ +#import "CredentialWindowController.h" + +@interface CredentialWindowController () + +@property (readwrite) VNCAuthenticationType authenticationType; +@property (readwrite, strong) NSString* previousUsername; +@property (readwrite, strong) NSString* previousPassword; + +@property (strong, nullable) NSWindow* parentWindow; +@property BOOL didLoad; + +@property (weak) IBOutlet NSTextField* textFieldUsername; +@property (weak) IBOutlet NSSecureTextField* textFieldPassword; + +@property (weak) IBOutlet NSButton* buttonOK; +@property (weak) IBOutlet NSButton* buttonCancel; + +@end + +@implementation CredentialWindowController + +- (NSNibName)windowNibName { + return @"CredentialWindow"; +} + +- (instancetype)initWithAuthenticationType:(VNCAuthenticationType)authenticationType + previousUsername:(NSString *)previousUsername + previousPassword:(NSString *)previousPassword { + self = [super initWithWindow:nil]; + + if (self) { + self.authenticationType = authenticationType; + self.previousUsername = previousUsername; + self.previousPassword = previousPassword; + } + + return self; +} + +- (void)beginSheetForParentWindow:(NSWindow*)parentWindow + completionHandler:(void (^)(__nullable id credential))completionHandler { + if (!self.window) { + completionHandler(nil); + + return; + } + + self.parentWindow = parentWindow; + + __weak CredentialWindowController* weakSelf = self; + + [parentWindow beginSheet:self.window completionHandler:^(NSModalResponse modalResponse) { + if (!weakSelf || + modalResponse != NSModalResponseOK) { + completionHandler(nil); + + return; + } + + CredentialWindowController* strongSelf = weakSelf; + + completionHandler(strongSelf.credential); + }]; +} + +- (void)windowDidLoad { + [super windowDidLoad]; + + if (self.didLoad) { + return; + } + + self.didLoad = YES; + + [self configureUI]; +} + +- (IBAction)buttonOK_action:(id)sender { + [self handleButtonClicked:NSModalResponseOK]; +} + +- (IBAction)buttonCancel_action:(id)sender { + [self handleButtonClicked:NSModalResponseCancel]; +} + +- (void)configureUI { + VNCAuthenticationType authenticationType = self.authenticationType; + + BOOL requiresUsername = [VNCAuthenticationTypeUtils authenticationTypeRequiresUsername:authenticationType]; + BOOL requiresPassword = [VNCAuthenticationTypeUtils authenticationTypeRequiresPassword:authenticationType]; + + self.textFieldUsername.enabled = requiresUsername; + self.textFieldPassword.enabled = requiresPassword; + + self.username = self.previousUsername; + self.password = self.previousPassword; + + if (requiresUsername) { + [self.window makeFirstResponder:self.textFieldUsername]; + } else { + [self.window makeFirstResponder:self.textFieldPassword]; + } +} + +- (void)handleButtonClicked:(NSModalResponse)modalResponse { + if (!self.parentWindow || + !self.window) { + return; + } + + [self.parentWindow endSheet:self.window + returnCode:modalResponse]; +} + +- (NSString*)username { + return self.textFieldUsername.stringValue; +} + +- (void)setUsername:(NSString *)username { + self.textFieldUsername.stringValue = username; +} + +- (NSString*)password { + return self.textFieldPassword.stringValue; +} + +- (void)setPassword:(NSString *)password { + self.textFieldPassword.stringValue = password; +} + +- (id)credential { + VNCAuthenticationType authenticationType = self.authenticationType; + BOOL requiresUsername = [VNCAuthenticationTypeUtils authenticationTypeRequiresUsername:authenticationType]; + + if (requiresUsername) { + return [[VNCUsernamePasswordCredential alloc] initWithUsername:self.username + password:self.password]; + } else { + return [[VNCPasswordCredential alloc] initWithPassword:self.password]; + } +} + +@end diff --git a/RoyalVNCObjCDemo/Source/Debug.xcconfig b/RoyalVNCObjCDemo/Source/Debug.xcconfig new file mode 100644 index 0000000..3455bcc --- /dev/null +++ b/RoyalVNCObjCDemo/Source/Debug.xcconfig @@ -0,0 +1 @@ +#include? "../Debug.xcconfig" diff --git a/RoyalVNCObjCDemo/Source/Release.xcconfig b/RoyalVNCObjCDemo/Source/Release.xcconfig new file mode 100644 index 0000000..f849f27 --- /dev/null +++ b/RoyalVNCObjCDemo/Source/Release.xcconfig @@ -0,0 +1 @@ +#include? "../Release.xcconfig" diff --git a/RoyalVNCObjCDemo/Source/RoyalVNCObjCDemo.entitlements b/RoyalVNCObjCDemo/Source/RoyalVNCObjCDemo.entitlements new file mode 100644 index 0000000..8cc185a --- /dev/null +++ b/RoyalVNCObjCDemo/Source/RoyalVNCObjCDemo.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.cs.disable-library-validation + + + diff --git a/RoyalVNCObjCDemo/Source/main.m b/RoyalVNCObjCDemo/Source/main.m new file mode 100644 index 0000000..842d1e1 --- /dev/null +++ b/RoyalVNCObjCDemo/Source/main.m @@ -0,0 +1,5 @@ +@import Cocoa; + +int main(int argc, const char * argv[]) { + return NSApplicationMain(argc, argv); +} diff --git a/RoyalVNCiOSDemo/RoyalVNCiOSDemo.xcodeproj/project.pbxproj b/RoyalVNCiOSDemo/RoyalVNCiOSDemo.xcodeproj/project.pbxproj new file mode 100644 index 0000000..eb9f18f --- /dev/null +++ b/RoyalVNCiOSDemo/RoyalVNCiOSDemo.xcodeproj/project.pbxproj @@ -0,0 +1,477 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 56; + objects = { + +/* Begin PBXBuildFile section */ + E79381F72CA72CE8005060BC /* RoyalVNCKit in Frameworks */ = {isa = PBXBuildFile; productRef = E79381F62CA72CE8005060BC /* RoyalVNCKit */; }; + E7B707AF28E32003005FE717 /* CredentialViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7B707AE28E32003005FE717 /* CredentialViewController.swift */; }; + E7B707B128E323E1005FE717 /* UIStoryboard+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7B707B028E323E1005FE717 /* UIStoryboard+Extensions.swift */; }; + E7B707B928E3310B005FE717 /* CAFramebufferViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7B707B828E3310B005FE717 /* CAFramebufferViewController.swift */; }; + E7B707BB28E33130005FE717 /* FramebufferViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7B707BA28E33130005FE717 /* FramebufferViewController.swift */; }; + E7B707C028E331CF005FE717 /* FramebufferViewControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7B707BF28E331CF005FE717 /* FramebufferViewControllerDelegate.swift */; }; + E7ED9E8E28DDD0C500F1BF80 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7ED9E8D28DDD0C500F1BF80 /* AppDelegate.swift */; }; + E7ED9E9028DDD0C500F1BF80 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7ED9E8F28DDD0C500F1BF80 /* SceneDelegate.swift */; }; + E7ED9E9228DDD0C500F1BF80 /* ConfigurationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7ED9E9128DDD0C500F1BF80 /* ConfigurationViewController.swift */; }; + E7ED9E9528DDD0C500F1BF80 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E7ED9E9328DDD0C500F1BF80 /* Main.storyboard */; }; + E7ED9E9728DDD0C600F1BF80 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E7ED9E9628DDD0C600F1BF80 /* Assets.xcassets */; }; + E7ED9E9A28DDD0C600F1BF80 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E7ED9E9828DDD0C600F1BF80 /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + E7AE880C29150F27001BAE18 /* Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + E7AE880F29151188001BAE18 /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + E7B707AE28E32003005FE717 /* CredentialViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CredentialViewController.swift; sourceTree = ""; }; + E7B707B028E323E1005FE717 /* UIStoryboard+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIStoryboard+Extensions.swift"; sourceTree = ""; }; + E7B707B828E3310B005FE717 /* CAFramebufferViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CAFramebufferViewController.swift; sourceTree = ""; }; + E7B707BA28E33130005FE717 /* FramebufferViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FramebufferViewController.swift; sourceTree = ""; }; + E7B707BF28E331CF005FE717 /* FramebufferViewControllerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FramebufferViewControllerDelegate.swift; sourceTree = ""; }; + E7ED9E8A28DDD0C500F1BF80 /* RoyalVNCiOSDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RoyalVNCiOSDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; + E7ED9E8D28DDD0C500F1BF80 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + E7ED9E8F28DDD0C500F1BF80 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + E7ED9E9128DDD0C500F1BF80 /* ConfigurationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationViewController.swift; sourceTree = ""; }; + E7ED9E9428DDD0C500F1BF80 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + E7ED9E9628DDD0C600F1BF80 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + E7ED9E9928DDD0C600F1BF80 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + E7ED9E9B28DDD0C600F1BF80 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + E7ED9EA228DDD0E000F1BF80 /* RoyalVNCKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = RoyalVNCKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + E7ED9E8728DDD0C500F1BF80 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + E79381F72CA72CE8005060BC /* RoyalVNCKit in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + E7B3136D28E5DE0D00C40684 /* Configuration */ = { + isa = PBXGroup; + children = ( + E7ED9E9128DDD0C500F1BF80 /* ConfigurationViewController.swift */, + ); + path = Configuration; + sourceTree = ""; + }; + E7B3136E28E5DE1300C40684 /* Credential */ = { + isa = PBXGroup; + children = ( + E7B707AE28E32003005FE717 /* CredentialViewController.swift */, + ); + path = Credential; + sourceTree = ""; + }; + E7B3136F28E5DE2000C40684 /* Connection */ = { + isa = PBXGroup; + children = ( + ); + path = Connection; + sourceTree = ""; + }; + E7B3137028E5DE2600C40684 /* Framebuffer */ = { + isa = PBXGroup; + children = ( + E7B707BA28E33130005FE717 /* FramebufferViewController.swift */, + E7B707BF28E331CF005FE717 /* FramebufferViewControllerDelegate.swift */, + E7B707B828E3310B005FE717 /* CAFramebufferViewController.swift */, + ); + path = Framebuffer; + sourceTree = ""; + }; + E7ED9E8128DDD0C500F1BF80 = { + isa = PBXGroup; + children = ( + E7ED9E8C28DDD0C500F1BF80 /* Source */, + E7ED9E8B28DDD0C500F1BF80 /* Products */, + E7ED9EA128DDD0E000F1BF80 /* Frameworks */, + ); + sourceTree = ""; + }; + E7ED9E8B28DDD0C500F1BF80 /* Products */ = { + isa = PBXGroup; + children = ( + E7ED9E8A28DDD0C500F1BF80 /* RoyalVNCiOSDemo.app */, + ); + name = Products; + sourceTree = ""; + }; + E7ED9E8C28DDD0C500F1BF80 /* Source */ = { + isa = PBXGroup; + children = ( + E7B3137028E5DE2600C40684 /* Framebuffer */, + E7B3136F28E5DE2000C40684 /* Connection */, + E7B3136E28E5DE1300C40684 /* Credential */, + E7B3136D28E5DE0D00C40684 /* Configuration */, + E7ED9E8D28DDD0C500F1BF80 /* AppDelegate.swift */, + E7ED9E8F28DDD0C500F1BF80 /* SceneDelegate.swift */, + E7B707B028E323E1005FE717 /* UIStoryboard+Extensions.swift */, + E7ED9E9328DDD0C500F1BF80 /* Main.storyboard */, + E7ED9E9828DDD0C600F1BF80 /* LaunchScreen.storyboard */, + E7ED9E9628DDD0C600F1BF80 /* Assets.xcassets */, + E7ED9E9B28DDD0C600F1BF80 /* Info.plist */, + E7AE880C29150F27001BAE18 /* Debug.xcconfig */, + E7AE880F29151188001BAE18 /* Release.xcconfig */, + ); + path = Source; + sourceTree = ""; + }; + E7ED9EA128DDD0E000F1BF80 /* Frameworks */ = { + isa = PBXGroup; + children = ( + E7ED9EA228DDD0E000F1BF80 /* RoyalVNCKit.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + E7ED9E8928DDD0C500F1BF80 /* RoyalVNCiOSDemo */ = { + isa = PBXNativeTarget; + buildConfigurationList = E7ED9E9E28DDD0C600F1BF80 /* Build configuration list for PBXNativeTarget "RoyalVNCiOSDemo" */; + buildPhases = ( + E7ED9E8628DDD0C500F1BF80 /* Sources */, + E7ED9E8728DDD0C500F1BF80 /* Frameworks */, + E7B3134B28E5871F00C40684 /* Swiftlint */, + E7ED9E8828DDD0C500F1BF80 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = RoyalVNCiOSDemo; + productName = RoyalVNCiOSDemo; + productReference = E7ED9E8A28DDD0C500F1BF80 /* RoyalVNCiOSDemo.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + E7ED9E8228DDD0C500F1BF80 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1400; + LastUpgradeCheck = 1600; + TargetAttributes = { + E7ED9E8928DDD0C500F1BF80 = { + CreatedOnToolsVersion = 14.0; + }; + }; + }; + buildConfigurationList = E7ED9E8528DDD0C500F1BF80 /* Build configuration list for PBXProject "RoyalVNCiOSDemo" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = E7ED9E8128DDD0C500F1BF80; + productRefGroup = E7ED9E8B28DDD0C500F1BF80 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + E7ED9E8928DDD0C500F1BF80 /* RoyalVNCiOSDemo */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + E7ED9E8828DDD0C500F1BF80 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + E7ED9E9A28DDD0C600F1BF80 /* LaunchScreen.storyboard in Resources */, + E7ED9E9728DDD0C600F1BF80 /* Assets.xcassets in Resources */, + E7ED9E9528DDD0C500F1BF80 /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + E7B3134B28E5871F00C40684 /* Swiftlint */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = Swiftlint; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "if which swiftlint >/dev/null; then\n\tswiftlint lint --config \"${PROJECT_DIR}/../.swiftlint.yml\"\nelse\n\techo \"Warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + E7ED9E8628DDD0C500F1BF80 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + E7B707C028E331CF005FE717 /* FramebufferViewControllerDelegate.swift in Sources */, + E7B707B928E3310B005FE717 /* CAFramebufferViewController.swift in Sources */, + E7B707B128E323E1005FE717 /* UIStoryboard+Extensions.swift in Sources */, + E7ED9E9228DDD0C500F1BF80 /* ConfigurationViewController.swift in Sources */, + E7ED9E8E28DDD0C500F1BF80 /* AppDelegate.swift in Sources */, + E7ED9E9028DDD0C500F1BF80 /* SceneDelegate.swift in Sources */, + E7B707BB28E33130005FE717 /* FramebufferViewController.swift in Sources */, + E7B707AF28E32003005FE717 /* CredentialViewController.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + E7ED9E9328DDD0C500F1BF80 /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + E7ED9E9428DDD0C500F1BF80 /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + E7ED9E9828DDD0C600F1BF80 /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + E7ED9E9928DDD0C600F1BF80 /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + E7ED9E9C28DDD0C600F1BF80 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = E7AE880C29150F27001BAE18 /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_APP_SANDBOX = YES; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + E7ED9E9D28DDD0C600F1BF80 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = E7AE880F29151188001BAE18 /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_APP_SANDBOX = YES; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + E7ED9E9F28DDD0C600F1BF80 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = E7AE880C29150F27001BAE18 /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = Source/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "RoyalVNC Demo"; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; + INFOPLIST_KEY_NSHumanReadableCopyright = "© 2022 Royal Apps GmbH"; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; + INFOPLIST_KEY_UIMainStoryboardFile = Main; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.royalapps.RoyalVNCiOSDemo; + PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + E7ED9EA028DDD0C600F1BF80 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = E7AE880F29151188001BAE18 /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = Source/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "RoyalVNC Demo"; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; + INFOPLIST_KEY_NSHumanReadableCopyright = "© 2022 Royal Apps GmbH"; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; + INFOPLIST_KEY_UIMainStoryboardFile = Main; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.royalapps.RoyalVNCiOSDemo; + PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + E7ED9E8528DDD0C500F1BF80 /* Build configuration list for PBXProject "RoyalVNCiOSDemo" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + E7ED9E9C28DDD0C600F1BF80 /* Debug */, + E7ED9E9D28DDD0C600F1BF80 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + E7ED9E9E28DDD0C600F1BF80 /* Build configuration list for PBXNativeTarget "RoyalVNCiOSDemo" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + E7ED9E9F28DDD0C600F1BF80 /* Debug */, + E7ED9EA028DDD0C600F1BF80 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCSwiftPackageProductDependency section */ + E79381F62CA72CE8005060BC /* RoyalVNCKit */ = { + isa = XCSwiftPackageProductDependency; + productName = RoyalVNCKit; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = E7ED9E8228DDD0C500F1BF80 /* Project object */; +} diff --git a/RoyalVNCiOSDemo/RoyalVNCiOSDemo.xcodeproj/xcshareddata/xcschemes/RoyalVNCiOSDemo.xcscheme b/RoyalVNCiOSDemo/RoyalVNCiOSDemo.xcodeproj/xcshareddata/xcschemes/RoyalVNCiOSDemo.xcscheme new file mode 100644 index 0000000..394d821 --- /dev/null +++ b/RoyalVNCiOSDemo/RoyalVNCiOSDemo.xcodeproj/xcshareddata/xcschemes/RoyalVNCiOSDemo.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/RoyalVNCiOSDemo/Source/AppDelegate.swift b/RoyalVNCiOSDemo/Source/AppDelegate.swift new file mode 100644 index 0000000..a72b55e --- /dev/null +++ b/RoyalVNCiOSDemo/Source/AppDelegate.swift @@ -0,0 +1,17 @@ +import Foundation +import UIKit + +@main +class AppDelegate: UIResponder, UIApplicationDelegate { + func application(_ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + return true + } + + func application(_ application: UIApplication, + configurationForConnecting connectingSceneSession: UISceneSession, + options: UIScene.ConnectionOptions) -> UISceneConfiguration { + return UISceneConfiguration(name: "Default Configuration", + sessionRole: connectingSceneSession.role) + } +} diff --git a/RoyalVNCiOSDemo/Source/Assets.xcassets/AccentColor.colorset/Contents.json b/RoyalVNCiOSDemo/Source/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..eb87897 --- /dev/null +++ b/RoyalVNCiOSDemo/Source/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/RoyalVNCiOSDemo/Source/Assets.xcassets/AppIcon.appiconset/AppIconiOS_1024.png b/RoyalVNCiOSDemo/Source/Assets.xcassets/AppIcon.appiconset/AppIconiOS_1024.png new file mode 100644 index 0000000..71464fd Binary files /dev/null and b/RoyalVNCiOSDemo/Source/Assets.xcassets/AppIcon.appiconset/AppIconiOS_1024.png differ diff --git a/RoyalVNCiOSDemo/Source/Assets.xcassets/AppIcon.appiconset/Contents.json b/RoyalVNCiOSDemo/Source/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..a22b699 --- /dev/null +++ b/RoyalVNCiOSDemo/Source/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,14 @@ +{ + "images" : [ + { + "filename" : "AppIconiOS_1024.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/RoyalVNCiOSDemo/Source/Assets.xcassets/Contents.json b/RoyalVNCiOSDemo/Source/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/RoyalVNCiOSDemo/Source/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/RoyalVNCiOSDemo/Source/Base.lproj/LaunchScreen.storyboard b/RoyalVNCiOSDemo/Source/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..865e932 --- /dev/null +++ b/RoyalVNCiOSDemo/Source/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/RoyalVNCiOSDemo/Source/Base.lproj/Main.storyboard b/RoyalVNCiOSDemo/Source/Base.lproj/Main.storyboard new file mode 100644 index 0000000..1168515 --- /dev/null +++ b/RoyalVNCiOSDemo/Source/Base.lproj/Main.storyboard @@ -0,0 +1,312 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/RoyalVNCiOSDemo/Source/Configuration/ConfigurationViewController.swift b/RoyalVNCiOSDemo/Source/Configuration/ConfigurationViewController.swift new file mode 100644 index 0000000..f7057ec --- /dev/null +++ b/RoyalVNCiOSDemo/Source/Configuration/ConfigurationViewController.swift @@ -0,0 +1,449 @@ +import Foundation +import UIKit + +import RoyalVNCKit + +class ConfigurationViewController: UIViewController { + @IBOutlet private weak var textFieldHostname: UITextField! + @IBOutlet private weak var textFieldPort: UITextField! + + @IBOutlet private weak var switchShared: UISwitch! + @IBOutlet private weak var switchClipboard: UISwitch! + @IBOutlet private weak var switchScaling: UISwitch! + @IBOutlet private weak var switchUseDisplayLink: UISwitch! + @IBOutlet private weak var switchDebugLogging: UISwitch! + @IBOutlet private weak var popupButtonInputMode: UIButton! + + @IBOutlet private weak var buttonConnect: UIButton! + + @IBOutlet private weak var imageViewStatus: UIImageView! + @IBOutlet private weak var textFieldStatus: UILabel! + + private var connection: VNCConnection? + + private var credentialViewController: CredentialViewController? + private var framebufferViewController: FramebufferViewController? + + override func viewDidLoad() { + super.viewDidLoad() + + settings = .fromUserDefaults() + } + + override func viewWillDisappear(_ animated: Bool) { + settings.saveToUserDefaults() + } + + @IBAction private func buttonConnect_touchUpInside(_ sender: Any) { + if connection != nil { + disconnect() + } else { + connect() + } + } +} + +// MARK: - Settings +private extension ConfigurationViewController { + var settings: VNCConnection.Settings { + get { + .init(isDebugLoggingEnabled: isDebugLoggingEnabled, + hostname: hostname, + port: port, + isShared: isShared, + isScalingEnabled: isScalingEnabled, + useDisplayLink: useDisplayLink, + inputMode: inputMode, + isClipboardRedirectionEnabled: isClipboardRedirectionEnabled, + colorDepth: colorDepth, + frameEncodings: frameEncodings) + } + set { + isDebugLoggingEnabled = newValue.isDebugLoggingEnabled + hostname = newValue.hostname + port = newValue.port + isShared = newValue.isShared + isScalingEnabled = newValue.isScalingEnabled + useDisplayLink = newValue.useDisplayLink + inputMode = newValue.inputMode + isClipboardRedirectionEnabled = newValue.isClipboardRedirectionEnabled + colorDepth = newValue.colorDepth + frameEncodings = newValue.frameEncodings + } + } + + var isDebugLoggingEnabled: Bool { + get { switchDebugLogging.isOn } + set { switchDebugLogging.isOn = newValue } + } + + var hostname: String { + get { textFieldHostname.text ?? "" } + set { textFieldHostname.text = newValue } + } + + var port: UInt16 { + get { .init(textFieldPort.text ?? "5900") ?? 5900 } + set { textFieldPort.text = .init(newValue) } + } + + var isShared: Bool { + get { switchShared.isOn } + set { switchShared.isOn = newValue } + } + + var isScalingEnabled: Bool { + get { switchScaling.isOn } + set { switchScaling.isOn = newValue } + } + + var useDisplayLink: Bool { + get { switchUseDisplayLink.isOn } + set { switchUseDisplayLink.isOn = newValue } + } + + var inputMode: VNCConnection.Settings.InputMode { + get { + // TODO + .forwardAllKeyboardShortcutsAndHotKeys + } + // swiftlint:disable:next unused_setter_value + set { + // TODO + } + } + + var isClipboardRedirectionEnabled: Bool { + get { switchClipboard.isOn } + set { switchClipboard.isOn = newValue } + } + + var colorDepth: VNCConnection.Settings.ColorDepth { + get { + // TODO + .depth24Bit + } + // swiftlint:disable:next unused_setter_value + set { + // TODO + } + } + + var frameEncodings: [VNCFrameEncodingType] { + get { + // TODO + .default + } + // swiftlint:disable:next unused_setter_value + set { + // TODO + } + } +} + +// MARK: - State +private extension ConfigurationViewController { + var statusText: String { + get { textFieldStatus.text ?? "" } + set { textFieldStatus.text = newValue } + } + + var statusImage: UIImage { + get { imageViewStatus.image ?? UIImage(systemName: "stop")! } + set { imageViewStatus.image = newValue } + } + + var connectButtonText: String { + get { buttonConnect.currentTitle ?? "" } + set { buttonConnect.setTitle(newValue, for: .normal) } + } + + var connectButtonIsEnabled: Bool { + get { buttonConnect.isEnabled } + set { buttonConnect.isEnabled = newValue } + } +} + +// MARK: - Connection +private extension ConfigurationViewController { + func connect() { + settings.saveToUserDefaults() + + destroyConnection() + + destroyFramebufferView() + + let settings = self.settings + + let connection = VNCConnection(settings: settings) + connection.delegate = self + + self.connection = connection + + connection.connect() + } + + func disconnect() { + guard connection?.connectionState.status != .disconnected else { return } + + connection?.disconnect() + } + + func destroyConnection() { + connection?.delegate = nil + + connection = nil + } + + func createFramebufferViewController(size: CGSize, + isScalingEnabled: Bool) { + if let oldViewController = framebufferViewController { + oldViewController.framebufferViewControllerDelegate = nil + oldViewController.dismiss(animated: true) + } + + framebufferViewController = nil + + guard let storyboard = storyboard else { + fatalError("No storyboard") + } + + let viewController = storyboard.caFramebufferViewController() + // TODO: CAFramebufferViewController(frame: rect, framebufferSize: size) + + if isScalingEnabled { + viewController.view.autoresizingMask = [ + .flexibleLeftMargin, .flexibleRightMargin, + .flexibleTopMargin, .flexibleBottomMargin, + .flexibleWidth, .flexibleHeight + ] + } + + viewController.logger = connection?.logger + viewController.settings = settings + + // TODO +// if isScalingEnabled { +// scrollView.hasVerticalScroller = false +// scrollView.hasHorizontalScroller = false +// } else { +// scrollView.hasVerticalScroller = true +// scrollView.hasHorizontalScroller = true +// } +// +// scrollView.documentView = view + + viewController.modalPresentationStyle = .overFullScreen + viewController.modalTransitionStyle = .crossDissolve + + present(viewController, + animated: true) + + viewController.framebufferViewControllerDelegate = self + + framebufferViewController = viewController + } + + func destroyFramebufferView() { + guard let framebufferViewController = framebufferViewController else { return } + + framebufferViewController.framebufferViewControllerDelegate = nil + + framebufferViewController.dismiss(animated: true) + + self.framebufferViewController = nil + } +} + +// MARK: - VNCConnectionDelegate +extension ConfigurationViewController: VNCConnectionDelegate { + func connection(_ connection: VNCConnection, + stateDidChange connectionState: VNCConnection.ConnectionState) { + let statusText: String + let statusImage: UIImage + + let buttonText: String + let buttonEnabled: Bool + + var destroyConnection = false + + switch connectionState.status { + case .connecting: + statusText = "Connecting…" + statusImage = .init(systemName: "shuffle")! + + buttonText = "Disconnect" + buttonEnabled = true + case .disconnecting: + statusText = "Disconnecting…" + statusImage = .init(systemName: "shuffle")! + + buttonText = "Disconnect" + buttonEnabled = false + case .connected: + statusText = "Connected" + statusImage = .init(systemName: "play")! + + buttonText = "Disconnect" + buttonEnabled = true + case .disconnected: + destroyConnection = true + + if let error = connectionState.error { + statusText = "Disconnected with Error: \(error.localizedDescription)" + statusImage = .init(systemName: "exclamationmark.triangle")! + } else { + statusText = "Disconnected" + statusImage = .init(systemName: "stop")! + } + + buttonText = "Connect" + buttonEnabled = true + } + + DispatchQueue.main.async { [weak self] in + guard let strongSelf = self else { return } + + strongSelf.statusImage = statusImage + strongSelf.statusText = statusText + + strongSelf.connectButtonText = buttonText + strongSelf.connectButtonIsEnabled = buttonEnabled + + if destroyConnection { + strongSelf.destroyFramebufferView() + strongSelf.destroyConnection() + } + } + } + + func connection(_ connection: VNCConnection, + credentialFor authenticationType: VNCAuthenticationType, + completion: @escaping (VNCCredential?) -> Void) { + DispatchQueue.main.async { [weak self] in + guard let strongSelf = self, + let storyboard = strongSelf.storyboard else { + completion(nil) + + return + } + + let settings = connection.settings + + let viewController = storyboard.credentialViewController() + + viewController.authenticationType = authenticationType + + let cachedUsername = settings.cachedUsername + let cachedPassword = settings.cachedPassword + + viewController.previousUsername = cachedUsername + viewController.previousPassword = cachedPassword + + viewController.modalPresentationStyle = .overCurrentContext + viewController.modalTransitionStyle = .crossDissolve + + strongSelf.credentialViewController = viewController + + viewController.completion = { credential in + strongSelf.credentialViewController = nil + + if let credential = credential { + if let userPassCred = credential as? VNCUsernamePasswordCredential { + if userPassCred.username != cachedUsername { + settings.cachedUsername = userPassCred.username + } + + if userPassCred.password != cachedPassword { + settings.cachedPassword = userPassCred.password + } + } else if let passCred = credential as? VNCPasswordCredential { + if passCred.password != cachedPassword { + settings.cachedPassword = passCred.password + } + } + } + + completion(credential) + } + + strongSelf.present(viewController, + animated: true) + } + } + + func connection(_ connection: VNCConnection, + didCreateFramebuffer framebuffer: VNCFramebuffer) { + DispatchQueue.main.async { [weak self] in + guard let strongSelf = self else { return } + + strongSelf.createFramebufferViewController(size: framebuffer.size.cgSize, + isScalingEnabled: connection.settings.isScalingEnabled) + } + } + + func connection(_ connection: VNCConnection, + didResizeFramebuffer framebuffer: VNCFramebuffer) { + // TODO + } + + func connection(_ connection: VNCConnection, + framebuffer: VNCFramebuffer, + didUpdateRegion updatedRegion: CGRect) { + framebufferViewController?.framebuffer(framebuffer, + didUpdateRegion: updatedRegion) + } + + func connection(_ connection: VNCConnection, + didUpdateCursor cursor: VNCCursor) { + // TODO: Support cursors on iOS + } +} + +// MARK: - Framebuffer View Delegate +extension ConfigurationViewController: FramebufferViewControllerDelegate { + func framebufferViewControllerDidRequestDisconnect(_ framebufferViewController: FramebufferViewController) { + connection?.disconnect() + } + + func framebufferViewController(_ framebufferViewController: FramebufferViewController, + mouseDidMove mousePosition: CGPoint) { + + } + + func framebufferViewController(_ framebufferViewController: FramebufferViewController, + mouseDownAt mousePosition: CGPoint) { + + } + + func framebufferViewController(_ framebufferViewController: FramebufferViewController, + mouseUpAt mousePosition: CGPoint) { + + } + + func framebufferViewController(_ framebufferViewController: FramebufferViewController, + rightMouseDownAt mousePosition: CGPoint) { + + } + + func framebufferViewController(_ framebufferViewController: FramebufferViewController, + rightMouseUpAt mousePosition: CGPoint) { + + } + + func framebufferViewController(_ framebufferViewController: FramebufferViewController, + scrollDelta: CGPoint, + mousePosition: CGPoint) { + + } + + func framebufferViewController(_ framebufferViewController: FramebufferViewController, + keyDown key: VNCKeyCode) { + connection?.keyDown(key) + } + + func framebufferViewController(_ framebufferViewController: FramebufferViewController, + keyUp key: VNCKeyCode) { + connection?.keyUp(key) + } +} diff --git a/RoyalVNCiOSDemo/Source/Credential/CredentialViewController.swift b/RoyalVNCiOSDemo/Source/Credential/CredentialViewController.swift new file mode 100644 index 0000000..1f0cc23 --- /dev/null +++ b/RoyalVNCiOSDemo/Source/Credential/CredentialViewController.swift @@ -0,0 +1,87 @@ +import Foundation +import UIKit + +import RoyalVNCKit + +class CredentialViewController: UIViewController { + var authenticationType: VNCAuthenticationType? + + var previousUsername: String? + var previousPassword: String? + + var completion: ((_ credential: VNCCredential?) -> Void)? + + private var didLoad = false + + @IBOutlet private weak var textFieldUsername: UITextField! + @IBOutlet private weak var textFieldPassword: UITextField! + + private enum Button { + case ok + case cancel + } + + override func viewDidLoad() { + super.viewDidLoad() + + guard !didLoad else { return } + didLoad = true + + configureUI() + } + + @IBAction private func buttonCancel_touchUpInside(_ sender: Any) { + handleButtonClicked(.cancel) + } + + @IBAction private func buttonOK_touchUpInside(_ sender: Any) { + handleButtonClicked(.ok) + } +} + +private extension CredentialViewController { + func configureUI() { + definesPresentationContext = true + + textFieldUsername.isEnabled = authenticationType?.requiresUsername ?? false + textFieldPassword.isEnabled = authenticationType?.requiresPassword ?? false + + username = previousUsername ?? "" + password = previousPassword ?? "" + + if authenticationType?.requiresUsername ?? false { + textFieldUsername.becomeFirstResponder() + } else { + textFieldPassword.becomeFirstResponder() + } + } + + private func handleButtonClicked(_ button: Button) { + dismiss(animated: true) + + let credential = button == .ok + ? self.credential + : nil + + completion?(credential) + } + + var credential: VNCCredential { + if authenticationType?.requiresUsername ?? false { + return VNCUsernamePasswordCredential(username: username, + password: password) + } else { + return VNCPasswordCredential(password: password) + } + } + + var username: String { + get { textFieldUsername.text ?? "" } + set { textFieldUsername.text = newValue } + } + + var password: String { + get { textFieldPassword.text ?? "" } + set { textFieldPassword.text = newValue } + } +} diff --git a/RoyalVNCiOSDemo/Source/Debug.xcconfig b/RoyalVNCiOSDemo/Source/Debug.xcconfig new file mode 100644 index 0000000..3455bcc --- /dev/null +++ b/RoyalVNCiOSDemo/Source/Debug.xcconfig @@ -0,0 +1 @@ +#include? "../Debug.xcconfig" diff --git a/RoyalVNCiOSDemo/Source/Framebuffer/CAFramebufferViewController.swift b/RoyalVNCiOSDemo/Source/Framebuffer/CAFramebufferViewController.swift new file mode 100644 index 0000000..7afe7a8 --- /dev/null +++ b/RoyalVNCiOSDemo/Source/Framebuffer/CAFramebufferViewController.swift @@ -0,0 +1,155 @@ +import Foundation +import UIKit + +import RoyalVNCKit + +class CAFramebufferViewController: UIViewController, FramebufferViewController { + weak var framebufferViewControllerDelegate: FramebufferViewControllerDelegate? + + var logger: VNCLogger? + var settings: VNCConnection.Settings? + + private(set) var framebufferSize: CGSize = .zero + + private var didLoad = false + + override var canBecomeFirstResponder: Bool { return true } + override var canResignFirstResponder: Bool { return true } + + private var textField = UITextField(frame: .init(x: -9999, y: -9999, width: 100, height: 20)) + + var scaleRatio: CGFloat { + let containerBounds = view.bounds + let fbSize = framebufferSize + + guard containerBounds.width > 0, + containerBounds.height > 0, + fbSize.width > 0, + fbSize.height > 0 else { + return 1 + } + + let targetAspectRatio = containerBounds.width / containerBounds.height + let fbAspectRatio = fbSize.width / fbSize.height + + let ratio: CGFloat + + if fbAspectRatio >= targetAspectRatio { + ratio = containerBounds.width / framebufferSize.width + } else { + ratio = containerBounds.height / framebufferSize.height + } + + // Only allow downscaling, no upscaling + guard ratio < 1 else { return 1 } + + return ratio + } + + var contentRect: CGRect { + let containerBounds = view.bounds + let scale = scaleRatio + + var rect = CGRect(x: 0, y: 0, + width: framebufferSize.width * scale, height: framebufferSize.height * scale) + + if rect.size.width < containerBounds.size.width { + rect.origin.x = (containerBounds.size.width - rect.size.width) / 2.0 + } + + if rect.size.height < containerBounds.size.height { + rect.origin.y = (containerBounds.size.height - rect.size.height) / 2.0 + } + + return rect + } + + override func viewDidLoad() { + super.viewDidLoad() + + guard !didLoad else { return } + didLoad = true + + let layer = view.layer + + layer.contentsScale = 1 + layer.contentsGravity = .center + layer.contentsFormat = .RGBA8Uint + + layer.minificationFilter = .trilinear + layer.magnificationFilter = .trilinear + + textField.autocorrectionType = .no + textField.autocapitalizationType = .none + textField.textContentType = .none + + textField.delegate = self + + view.addSubview(textField) + textField.becomeFirstResponder() + } + + @IBAction private func buttonDisconnect_touchUpInside(_ sender: Any) { + framebufferViewControllerDelegate?.framebufferViewControllerDidRequestDisconnect(self) + } +} + +extension CAFramebufferViewController { + func frameSizeDidChange(_ size: CGSize) { + guard settings?.isScalingEnabled ?? true else { + return + } + + if frameSizeExceedsFramebufferSize(size) { + // Don't allow upscaling + view.layer.contentsGravity = .center + } else { + // Allow downscaling + view.layer.contentsGravity = .resizeAspect + } + } + + func framebuffer(_ framebuffer: VNCFramebuffer, + didUpdateRegion updatedRegion: CGRect) { + let cgImage = framebuffer.cgImage + + DispatchQueue.main.async { [weak self] in + guard let strongSelf = self else { return } + + let layer = strongSelf.view.layer + + layer.contents = cgImage + } + } +} + +extension CAFramebufferViewController: UITextFieldDelegate { + func textFieldShouldReturn(_ textField: UITextField) -> Bool { + let key = VNCKeyCode.return + + handleKeyPress(key) + + return true + } + + func textFieldShouldClear(_ textField: UITextField) -> Bool { + // TODO: Doesn't appear to do what we want + let key = VNCKeyCode.delete + + handleKeyPress(key) + + return true + } + + func textField(_ textField: UITextField, + shouldChangeCharactersIn range: NSRange, + replacementString string: String) -> Bool { + let keys = VNCKeyCode.keyCodesFrom(characters: string) + + for key in keys { + handleKeyPress(key) + } + + return true + } +} diff --git a/RoyalVNCiOSDemo/Source/Framebuffer/FramebufferViewController.swift b/RoyalVNCiOSDemo/Source/Framebuffer/FramebufferViewController.swift new file mode 100644 index 0000000..1575508 --- /dev/null +++ b/RoyalVNCiOSDemo/Source/Framebuffer/FramebufferViewController.swift @@ -0,0 +1,40 @@ +import Foundation +import UIKit + +import RoyalVNCKit + +protocol FramebufferViewController: UIViewController { + var framebufferSize: CGSize { get } + + var scaleRatio: CGFloat { get } + var contentRect: CGRect { get } + +// var lastModifierFlags: NSEvent.ModifierFlags { get set } + + var framebufferViewControllerDelegate: FramebufferViewControllerDelegate? { get set } + + func framebuffer(_ framebuffer: VNCFramebuffer, + didUpdateRegion updatedRegion: CGRect) +} + +extension FramebufferViewController { + func frameSizeExceedsFramebufferSize(_ frameSize: CGSize) -> Bool { + return frameSize.width >= framebufferSize.width && + frameSize.height >= framebufferSize.height + } + + func handleKeyPress(_ key: VNCKeyCode) { + handleKeyDown(key) + handleKeyUp(key) + } + + func handleKeyDown(_ key: VNCKeyCode) { + framebufferViewControllerDelegate?.framebufferViewController(self, + keyDown: key) + } + + func handleKeyUp(_ key: VNCKeyCode) { + framebufferViewControllerDelegate?.framebufferViewController(self, + keyUp: key) + } +} diff --git a/RoyalVNCiOSDemo/Source/Framebuffer/FramebufferViewControllerDelegate.swift b/RoyalVNCiOSDemo/Source/Framebuffer/FramebufferViewControllerDelegate.swift new file mode 100644 index 0000000..f2e084f --- /dev/null +++ b/RoyalVNCiOSDemo/Source/Framebuffer/FramebufferViewControllerDelegate.swift @@ -0,0 +1,33 @@ +import Foundation +import UIKit + +import RoyalVNCKit + +protocol FramebufferViewControllerDelegate: AnyObject { + func framebufferViewControllerDidRequestDisconnect(_ framebufferViewController: FramebufferViewController) + + func framebufferViewController(_ framebufferViewController: FramebufferViewController, + mouseDidMove mousePosition: CGPoint) + + func framebufferViewController(_ framebufferViewController: FramebufferViewController, + mouseDownAt mousePosition: CGPoint) + + func framebufferViewController(_ framebufferViewController: FramebufferViewController, + mouseUpAt mousePosition: CGPoint) + + func framebufferViewController(_ framebufferViewController: FramebufferViewController, + rightMouseDownAt mousePosition: CGPoint) + + func framebufferViewController(_ framebufferViewController: FramebufferViewController, + rightMouseUpAt mousePosition: CGPoint) + + func framebufferViewController(_ framebufferViewController: FramebufferViewController, + scrollDelta: CGPoint, + mousePosition: CGPoint) + + func framebufferViewController(_ framebufferViewController: FramebufferViewController, + keyDown key: VNCKeyCode) + + func framebufferViewController(_ framebufferViewController: FramebufferViewController, + keyUp key: VNCKeyCode) +} diff --git a/RoyalVNCiOSDemo/Source/Info.plist b/RoyalVNCiOSDemo/Source/Info.plist new file mode 100644 index 0000000..dd3c9af --- /dev/null +++ b/RoyalVNCiOSDemo/Source/Info.plist @@ -0,0 +1,25 @@ + + + + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + UISceneStoryboardFile + Main + + + + + + diff --git a/RoyalVNCiOSDemo/Source/Release.xcconfig b/RoyalVNCiOSDemo/Source/Release.xcconfig new file mode 100644 index 0000000..f849f27 --- /dev/null +++ b/RoyalVNCiOSDemo/Source/Release.xcconfig @@ -0,0 +1 @@ +#include? "../Release.xcconfig" diff --git a/RoyalVNCiOSDemo/Source/SceneDelegate.swift b/RoyalVNCiOSDemo/Source/SceneDelegate.swift new file mode 100644 index 0000000..27724e1 --- /dev/null +++ b/RoyalVNCiOSDemo/Source/SceneDelegate.swift @@ -0,0 +1,6 @@ +import Foundation +import UIKit + +class SceneDelegate: UIResponder, UIWindowSceneDelegate { + var window: UIWindow? +} diff --git a/RoyalVNCiOSDemo/Source/UIStoryboard+Extensions.swift b/RoyalVNCiOSDemo/Source/UIStoryboard+Extensions.swift new file mode 100644 index 0000000..63cc79f --- /dev/null +++ b/RoyalVNCiOSDemo/Source/UIStoryboard+Extensions.swift @@ -0,0 +1,20 @@ +import Foundation +import UIKit + +extension UIStoryboard { + func credentialViewController() -> CredentialViewController { + guard let ctrl = instantiateViewController(withIdentifier: "CredentialViewController") as? CredentialViewController else { + fatalError("Failed to instantiate view controller") + } + + return ctrl + } + + func caFramebufferViewController() -> CAFramebufferViewController { + guard let ctrl = instantiateViewController(withIdentifier: "CAFramebufferViewController") as? CAFramebufferViewController else { + fatalError("Failed to instantiate view controller") + } + + return ctrl + } +}