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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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
+ }
+}