diff --git a/Podfile b/Podfile index ad3ccec..3e3eec4 100644 --- a/Podfile +++ b/Podfile @@ -1,6 +1,8 @@ target "VirtualKVM" do + use_frameworks! pod 'GVUserDefaults' pod 'Sparkle' + pod 'SBObjectiveCWrapper', '~> 1.4.0' end target "VirtualKVMTests" do diff --git a/Podfile.lock b/Podfile.lock index d580da9..62865dc 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -1,15 +1,21 @@ PODS: - GVUserDefaults (1.0.2) + - SBObjectiveCWrapper (1.4.0): + - SwiftyBeaver (~> 1.4.0) - Sparkle (1.18.1) + - SwiftyBeaver (1.4.4) DEPENDENCIES: - GVUserDefaults + - SBObjectiveCWrapper (~> 1.4.0) - Sparkle SPEC CHECKSUMS: GVUserDefaults: 52d48cf8ba578b40a1a76312b196dfc9134ba36a + SBObjectiveCWrapper: 854ea56b37cd0031af2b1d6e12c2f965fc4ff86d Sparkle: 06ea33170007c5937ee54da481b4481af98fac79 + SwiftyBeaver: 25bd76281f49ca989ec2e3cbde9af89c15bc1432 -PODFILE CHECKSUM: b008d844f4ab0f3d8de00788eaa63a46c7c44e29 +PODFILE CHECKSUM: bd5504958af33c674f630d6e7d19e13b376df9df -COCOAPODS: 1.2.0 +COCOAPODS: 1.3.1 diff --git a/README.md b/README.md index a1c9c59..6ddd232 100644 --- a/README.md +++ b/README.md @@ -27,10 +27,12 @@ This project requires CocoaPods. * sudo gem install cocoapods * pod install -Open VirtualKVM.xcworkspace in XCode and build... +Open VirtualKVM.xcworkspace in Xcode and build... Known Issues ============ -When bluetooth is disabled, if the macbook goes to sleep, the iMac failes to re-instate target display mode, I THINK because it no longer has a keyboard and thus I can't send keyboard command sequences, which is the only way I know how to trigger +* Bluetooth switching between newer Apple wireless (keyboards, mice, trackpads) is unsupported. + +* When bluetooth is disabled, if the macbook goes to sleep, the iMac failes to re-instate target display mode, I THINK because it no longer has a keyboard and thus I can't send keyboard command sequences, which is the only way I know how to trigger target display mode. Let me know if you have any ideas. diff --git a/Releases/CHANGELOG.html b/Releases/CHANGELOG.html new file mode 100644 index 0000000..9a1d89a --- /dev/null +++ b/Releases/CHANGELOG.html @@ -0,0 +1,28 @@ + + + + + + + +

VirtualKVM 1.2.4

+ +

Released: Jun 07, 2018

+ + + +

VirtualKVM 1.2.3

+ +

Released: Nov 06, 2017

+ + + + + \ No newline at end of file diff --git a/Releases/CHANGELOG.md b/Releases/CHANGELOG.md index 804b72a..aa1eaf1 100644 --- a/Releases/CHANGELOG.md +++ b/Releases/CHANGELOG.md @@ -1,3 +1,11 @@ +### VirtualKVM 1.2.4 + +**Released: Jun 07, 2018** + +* Now requires macOS 10.11 or later +* Add ability to save debug logs +* Should fix connectivity issues + ### VirtualKVM 1.2.3 **Released: Nov 06, 2017** diff --git a/Releases/appcast.xml b/Releases/appcast.xml index b9f1f9b..55f0e78 100644 --- a/Releases/appcast.xml +++ b/Releases/appcast.xml @@ -6,6 +6,14 @@ Most recent changes with links to updates. en + + 124 + 10.11 + https://github.com/duanefields/VirtualKVM/raw/master/Releases/CHANGELOG.html + Thu, 07 Nov 2018 02:50:00 +0000 + + + 123 - \ No newline at end of file + diff --git a/Releases/v1_2_4.zip b/Releases/v1_2_4.zip new file mode 100644 index 0000000..17adfdd Binary files /dev/null and b/Releases/v1_2_4.zip differ diff --git a/VirtualKVM.xcodeproj/project.pbxproj b/VirtualKVM.xcodeproj/project.pbxproj index c6b9abf..193c2fc 100644 --- a/VirtualKVM.xcodeproj/project.pbxproj +++ b/VirtualKVM.xcodeproj/project.pbxproj @@ -7,6 +7,9 @@ objects = { /* Begin PBXBuildFile section */ + 011BCBEE20B25B20008D78DC /* NetworkInterfaceNotifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 011BCBED20B25B20008D78DC /* NetworkInterfaceNotifier.swift */; }; + 011BCBF020B25B35008D78DC /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 011BCBEF20B25B35008D78DC /* SystemConfiguration.framework */; }; + 01C05AC220C08BC0002E0C89 /* Uiltites.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01C05AC120C08BC0002E0C89 /* Uiltites.swift */; }; 2840E5131DD8CD940054146D /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 2840E5151DD8CD940054146D /* Localizable.strings */; }; 2854907C1DD8610800525808 /* KVMSystemProfiler.m in Sources */ = {isa = PBXBuildFile; fileRef = 2854907B1DD8610800525808 /* KVMSystemProfiler.m */; }; 47672946191BBD7200726705 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 47672945191BBD7200726705 /* Cocoa.framework */; }; @@ -26,7 +29,7 @@ 47E96804191C3A17006531E1 /* KVMBluetoothController.m in Sources */ = {isa = PBXBuildFile; fileRef = 47E96803191C3A17006531E1 /* KVMBluetoothController.m */; }; 47E96806191C3AFB006531E1 /* IOBluetooth.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 47E96805191C3AFB006531E1 /* IOBluetooth.framework */; }; 47E9680A191DB6FA006531E1 /* GVUserDefaults+KVMApp.m in Sources */ = {isa = PBXBuildFile; fileRef = 47E96809191DB6FA006531E1 /* GVUserDefaults+KVMApp.m */; }; - 842C043D5E3184F3FD955214 /* libPods-VirtualKVM.a in Frameworks */ = {isa = PBXBuildFile; fileRef = ECB0AB336CC54A45593109BE /* libPods-VirtualKVM.a */; }; + EF7DCC2AA52E85D5C4536BE1 /* Pods_VirtualKVM.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 64CF363233439698EE2DC50D /* Pods_VirtualKVM.framework */; }; F57E3D68E03B79931505646C /* libPods-VirtualKVMTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1CDE0651EFEFF723A5BBA131 /* libPods-VirtualKVMTests.a */; }; /* End PBXBuildFile section */ @@ -41,6 +44,10 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 011BCBEC20B25B1F008D78DC /* VirtualKVM-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "VirtualKVM-Bridging-Header.h"; sourceTree = ""; }; + 011BCBED20B25B20008D78DC /* NetworkInterfaceNotifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkInterfaceNotifier.swift; sourceTree = ""; }; + 011BCBEF20B25B35008D78DC /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; }; + 01C05AC120C08BC0002E0C89 /* Uiltites.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Uiltites.swift; sourceTree = ""; }; 1CDE0651EFEFF723A5BBA131 /* libPods-VirtualKVMTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-VirtualKVMTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 2673095FE54EC9E82407FA63 /* Pods-VirtualKVMTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-VirtualKVMTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-VirtualKVMTests/Pods-VirtualKVMTests.debug.xcconfig"; sourceTree = ""; }; 2840E50D1DD8CD870054146D /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/InfoPlist.strings; sourceTree = ""; }; @@ -74,16 +81,16 @@ 4767297C191BF05400726705 /* KVMController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KVMController.h; sourceTree = ""; }; 4767297D191BF05400726705 /* KVMController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KVMController.m; sourceTree = ""; }; 4767297F191C022900726705 /* KVMThunderboltObserver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KVMThunderboltObserver.h; sourceTree = ""; }; - 47672980191C022900726705 /* KVMThunderboltObserver.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KVMThunderboltObserver.m; sourceTree = ""; }; + 47672980191C022900726705 /* KVMThunderboltObserver.m */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.c.objc; path = KVMThunderboltObserver.m; sourceTree = ""; }; 47E96802191C3A17006531E1 /* KVMBluetoothController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KVMBluetoothController.h; sourceTree = ""; }; 47E96803191C3A17006531E1 /* KVMBluetoothController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KVMBluetoothController.m; sourceTree = ""; }; 47E96805191C3AFB006531E1 /* IOBluetooth.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOBluetooth.framework; path = System/Library/Frameworks/IOBluetooth.framework; sourceTree = SDKROOT; }; 47E96808191DB6FA006531E1 /* GVUserDefaults+KVMApp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GVUserDefaults+KVMApp.h"; sourceTree = ""; }; 47E96809191DB6FA006531E1 /* GVUserDefaults+KVMApp.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GVUserDefaults+KVMApp.m"; sourceTree = ""; }; + 64CF363233439698EE2DC50D /* Pods_VirtualKVM.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_VirtualKVM.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 9928F567C93C6C6EB874E4BA /* Pods-VirtualKVM.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-VirtualKVM.release.xcconfig"; path = "Pods/Target Support Files/Pods-VirtualKVM/Pods-VirtualKVM.release.xcconfig"; sourceTree = ""; }; EAF7D17600776A7F7140AB1E /* Pods-VirtualKVMTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-VirtualKVMTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-VirtualKVMTests/Pods-VirtualKVMTests.release.xcconfig"; sourceTree = ""; }; EC87DECAF4D69C12AAF5B1F0 /* Pods-VirtualKVM.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-VirtualKVM.debug.xcconfig"; path = "Pods/Target Support Files/Pods-VirtualKVM/Pods-VirtualKVM.debug.xcconfig"; sourceTree = ""; }; - ECB0AB336CC54A45593109BE /* libPods-VirtualKVM.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-VirtualKVM.a"; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -91,9 +98,10 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 011BCBF020B25B35008D78DC /* SystemConfiguration.framework in Frameworks */, 47E96806191C3AFB006531E1 /* IOBluetooth.framework in Frameworks */, 47672946191BBD7200726705 /* Cocoa.framework in Frameworks */, - 842C043D5E3184F3FD955214 /* libPods-VirtualKVM.a in Frameworks */, + EF7DCC2AA52E85D5C4536BE1 /* Pods_VirtualKVM.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -130,6 +138,7 @@ 47672943191BBD7200726705 /* Products */, 36829DEFAD624FCB6420C463 /* Pods */, ); + indentWidth = 2; sourceTree = ""; }; 47672943191BBD7200726705 /* Products */ = { @@ -144,12 +153,13 @@ 47672944191BBD7200726705 /* Frameworks */ = { isa = PBXGroup; children = ( + 011BCBEF20B25B35008D78DC /* SystemConfiguration.framework */, 47E96805191C3AFB006531E1 /* IOBluetooth.framework */, 47672945191BBD7200726705 /* Cocoa.framework */, 47672964191BBD7200726705 /* XCTest.framework */, 47672947191BBD7200726705 /* Other Frameworks */, - ECB0AB336CC54A45593109BE /* libPods-VirtualKVM.a */, 1CDE0651EFEFF723A5BBA131 /* libPods-VirtualKVMTests.a */, + 64CF363233439698EE2DC50D /* Pods_VirtualKVM.framework */, ); name = Frameworks; sourceTree = ""; @@ -173,6 +183,7 @@ 47672957191BBD7200726705 /* KVMAppDelegate.h */, 47672958191BBD7200726705 /* KVMAppDelegate.m */, 47E96802191C3A17006531E1 /* KVMBluetoothController.h */, + 01C05AC120C08BC0002E0C89 /* Uiltites.swift */, 47E96803191C3A17006531E1 /* KVMBluetoothController.m */, 4767297C191BF05400726705 /* KVMController.h */, 4767297D191BF05400726705 /* KVMController.m */, @@ -180,8 +191,10 @@ 2854907B1DD8610800525808 /* KVMSystemProfiler.m */, 4767297F191C022900726705 /* KVMThunderboltObserver.h */, 47672980191C022900726705 /* KVMThunderboltObserver.m */, + 011BCBED20B25B20008D78DC /* NetworkInterfaceNotifier.swift */, 47E96807191DB6C1006531E1 /* Status Menu UI */, 4767294C191BBD7200726705 /* Supporting Files */, + 011BCBEC20B25B1F008D78DC /* VirtualKVM-Bridging-Header.h */, ); path = VirtualKVM; sourceTree = ""; @@ -281,6 +294,9 @@ LastUpgradeCheck = 0800; ORGANIZATIONNAME = "Fast Wombat"; TargetAttributes = { + 47672941191BBD7200726705 = { + LastSwiftMigration = 0930; + }; 47672962191BBD7200726705 = { TestTargetID = 47672941191BBD7200726705; }; @@ -345,7 +361,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; 499F7C25E03D884A58C90A9C /* [CP] Check Pods Manifest.lock */ = { @@ -363,7 +379,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; 58C91F90D2DB4CDD08B23BE9 /* [CP] Embed Pods Frameworks */ = { @@ -373,13 +389,19 @@ ); inputPaths = ( "${SRCROOT}/Pods/Target Support Files/Pods-VirtualKVM/Pods-VirtualKVM-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/GVUserDefaults/GVUserDefaults.framework", + "${BUILT_PRODUCTS_DIR}/SBObjectiveCWrapper/SBObjectiveCWrapper.framework", "${PODS_ROOT}/Sparkle/Sparkle.framework", "${PODS_ROOT}/Sparkle/Sparkle.framework.dSYM", + "${BUILT_PRODUCTS_DIR}/SwiftyBeaver/SwiftyBeaver.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GVUserDefaults.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SBObjectiveCWrapper.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Sparkle.framework", "${DWARF_DSYM_FOLDER_PATH}/Sparkle.framework.dSYM", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftyBeaver.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -441,8 +463,10 @@ 4767297E191BF05400726705 /* KVMController.m in Sources */, 47672952191BBD7200726705 /* main.m in Sources */, 47672981191C022900726705 /* KVMThunderboltObserver.m in Sources */, + 011BCBEE20B25B20008D78DC /* NetworkInterfaceNotifier.swift in Sources */, 47E9680A191DB6FA006531E1 /* GVUserDefaults+KVMApp.m in Sources */, 2854907C1DD8610800525808 /* KVMSystemProfiler.m in Sources */, + 01C05AC220C08BC0002E0C89 /* Uiltites.swift in Sources */, 47E96804191C3A17006531E1 /* KVMBluetoothController.m in Sources */, 47C0D521191EFA1A00BB9246 /* KVMStatusItem.m in Sources */, 47672959191BBD7200726705 /* KVMAppDelegate.m in Sources */, @@ -603,13 +627,18 @@ baseConfigurationReference = EC87DECAF4D69C12AAF5B1F0 /* Pods-VirtualKVM.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; COMBINE_HIDPI_IMAGES = YES; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "VirtualKVM/VirtualKVM-Prefix.pch"; INFOPLIST_FILE = "VirtualKVM/VirtualKVM-Info.plist"; - MACOSX_DEPLOYMENT_TARGET = 10.9; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.11; PRODUCT_BUNDLE_IDENTIFIER = "com.fastwombat.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "VirtualKVM/VirtualKVM-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 3.0; WRAPPER_EXTENSION = app; }; name = Debug; @@ -619,13 +648,17 @@ baseConfigurationReference = 9928F567C93C6C6EB874E4BA /* Pods-VirtualKVM.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; COMBINE_HIDPI_IMAGES = YES; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "VirtualKVM/VirtualKVM-Prefix.pch"; INFOPLIST_FILE = "VirtualKVM/VirtualKVM-Info.plist"; - MACOSX_DEPLOYMENT_TARGET = 10.9; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.11; PRODUCT_BUNDLE_IDENTIFIER = "com.fastwombat.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "VirtualKVM/VirtualKVM-Bridging-Header.h"; + SWIFT_VERSION = 3.0; WRAPPER_EXTENSION = app; }; name = Release; @@ -634,6 +667,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = 2673095FE54EC9E82407FA63 /* Pods-VirtualKVMTests.debug.xcconfig */; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/VirtualKVM.app/Contents/MacOS/VirtualKVM"; COMBINE_HIDPI_IMAGES = YES; FRAMEWORK_SEARCH_PATHS = ( @@ -658,6 +692,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = EAF7D17600776A7F7140AB1E /* Pods-VirtualKVMTests.release.xcconfig */; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/VirtualKVM.app/Contents/MacOS/VirtualKVM"; COMBINE_HIDPI_IMAGES = YES; FRAMEWORK_SEARCH_PATHS = ( diff --git a/VirtualKVM/Base.lproj/MainMenu.xib b/VirtualKVM/Base.lproj/MainMenu.xib index 1b99705..2d1e716 100644 --- a/VirtualKVM/Base.lproj/MainMenu.xib +++ b/VirtualKVM/Base.lproj/MainMenu.xib @@ -1,8 +1,8 @@ - + - + @@ -16,6 +16,7 @@ + @@ -49,6 +50,19 @@ + + + + + + + + + + + + + diff --git a/VirtualKVM/KVMAppDelegate.m b/VirtualKVM/KVMAppDelegate.m index fce53a7..293f850 100644 --- a/VirtualKVM/KVMAppDelegate.m +++ b/VirtualKVM/KVMAppDelegate.m @@ -2,14 +2,21 @@ #import "KVMStatusItem.h" #import "KVMController.h" #import "GVUserDefaults+KVMApp.h" +#import "KVMSystemProfiler.h" +@import SBObjectiveCWrapper; @interface KVMAppDelegate () @end - @implementation KVMAppDelegate - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { + + [[Uiltites shared]setupLogging]; + + SBLogInfo(@"App version: %@", [NSBundle mainBundle].infoDictionary[(NSString *)kCFBundleVersionKey]); + SBLogVerbose(@"Display info: %@", [KVMSystemProfiler dataTypes:@[@"SPDisplaysDataType"]].firstObject); + SBLogVerbose(@"Thunderbolt info: %@", [KVMSystemProfiler dataTypes:@[@"SPThunderboltDataType"]].firstObject); } @end diff --git a/VirtualKVM/KVMController.m b/VirtualKVM/KVMController.m index f891323..e0a8290 100644 --- a/VirtualKVM/KVMController.m +++ b/VirtualKVM/KVMController.m @@ -5,12 +5,14 @@ #import #include #include +@import SBObjectiveCWrapper; @interface KVMController () @property (nonatomic, strong) KVMThunderboltObserver *thunderboltObserver; @property (nonatomic, strong) NSStatusItem *statusItem; @property (nonatomic) IOPMAssertionID sleepAssertion; +@property (nonatomic, strong) id clientUserActivity; @property (nonatomic) BOOL isClient; @property (nonatomic) IBOutlet NSMenu *menu; @@ -19,6 +21,7 @@ @interface KVMController () @property (weak) IBOutlet NSMenuItem *toggleSleepMenuItem; @property (weak) IBOutlet NSMenuItem *connectionStatusMenuItem; @property (nonatomic, assign) CFStringRef assertionType; +@property (weak) IBOutlet NSMenuItem *saveDebugLogsMenuItem; @end @@ -27,7 +30,7 @@ @implementation KVMController + (NSString *)machineModel { size_t len = 0; sysctlbyname("hw.model", NULL, &len, NULL, 0); - + if (len) { char *model = malloc(len * sizeof(char)); sysctlbyname("hw.model", model, &len, NULL, 0); @@ -36,23 +39,31 @@ + (NSString *)machineModel { NSLog(@"Running on %@.", model_ns); return model_ns; } - + return @"Unknown"; } ++ (void)initialize { + [[Uiltites shared]setupLogging]; +} - (id)init { self = [super init]; self.isClient = [[KVMController machineModel] rangeOfString:@"iMac"].location == NSNotFound; + + if (!self.isClient) { + self.thunderboltObserver = [[KVMThunderboltObserver alloc] initWithDelegate:self]; + [self.thunderboltObserver startObserving]; - if (!self.isClient) { - self.thunderboltObserver = [[KVMThunderboltObserver alloc] initWithDelegate:self]; - [self.thunderboltObserver startObserving]; - } - - + self.clientUserActivity = [[NSProcessInfo processInfo]beginActivityWithOptions:NSActivityIdleSystemSleepDisabled reason:@"Checking for connections"]; + } + return self; } +- (void)dealloc { + [[NSProcessInfo processInfo]endActivity:self.clientUserActivity]; +} + - (NSString *)modeString { if (self.isClient) { return NSLocalizedString(@"Client Mode", comment:nil); @@ -66,9 +77,9 @@ - (void)awakeFromNib { self.toggleBluetoothMenuItem.state = [GVUserDefaults standardUserDefaults].toggleBluetooth ? NSOnState : NSOffState; self.toggleDisplayMenuItem.state = [GVUserDefaults standardUserDefaults].toggleTargetDisplayMode ? NSOnState : NSOffState; self.toggleSleepMenuItem.state = [GVUserDefaults standardUserDefaults].toggleDisableSleep ? NSOnState : NSOffState; - + self.connectionStatusMenuItem.title = [NSString stringWithFormat:@"%@: %@", [self modeString], NSLocalizedString(@"Initializing …", comment:"State when the application is initializing.")]; - + if (self.isClient) { NSLog(@"Running in client mode."); [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidChangeScreenParametersNotification:) name:NSApplicationDidChangeScreenParametersNotification object:nil]; @@ -79,55 +90,84 @@ - (void)awakeFromNib { self.toggleSleepMenuItem.hidden = YES; [GVUserDefaults standardUserDefaults].toggleDisableSleep = NO; } - + self.statusItem = [KVMStatusItem statusItemWithMenu:self.menu]; } #pragma mark - NSApplicationDidChangeScreenParametersNotification - (void)applicationDidChangeScreenParametersNotification:(NSNotification *)notifcation { - - [self updateConnectionState:[self clientIsInTargetDisplayMode]]; + + [self updateConnectionState:[self clientIsInTargetDisplayMode]]; } #pragma mark - Menu Actions - (IBAction)toggleTargetDisplayOption:(id)sender { NSMenuItem *menuItem = (NSMenuItem *)sender; - + if (menuItem.state == NSOnState) { menuItem.state = NSOffState; } else { menuItem.state = NSOnState; } - + [GVUserDefaults standardUserDefaults].toggleTargetDisplayMode = menuItem.state == NSOnState; } - (IBAction)toggleBluetoothOption:(id)sender { NSMenuItem *menuItem = (NSMenuItem *)sender; - + if (menuItem.state == NSOnState) { menuItem.state = NSOffState; } else { menuItem.state = NSOnState; } - + [GVUserDefaults standardUserDefaults].toggleBluetooth = menuItem.state == NSOnState; } - (IBAction)toggleSleepOption:(id)sender { NSMenuItem *menuItem = (NSMenuItem *)sender; - + if (menuItem.state == NSOnState) { menuItem.state = NSOffState; } else { menuItem.state = NSOnState; } - + [GVUserDefaults standardUserDefaults].toggleDisableSleep = menuItem.state == NSOnState; } +- (IBAction)toggleSaveDebugLogs:(id)sender { + + NSOpenPanel *openPanel = [NSOpenPanel openPanel]; + openPanel.canChooseFiles = NO; + openPanel.canChooseDirectories = YES; + openPanel.message = @"Choose where you would like to save the debug log"; + NSModalResponse modalResponse = [openPanel runModal]; + if (modalResponse != NSModalResponseOK) { return; } + + NSString *logFilePath = [[Uiltites shared]logFilePath]; + if (logFilePath == nil) { + NSAlert *alert = [NSAlert new]; + alert.messageText = @"Failed to save log"; + alert.informativeText = @"App failed to save the debug log. Please relaunch the app and try again."; + [alert runModal]; + return; + } + + NSString *destinationFilePath = [[[openPanel.directoryURL.path stringByAppendingPathComponent:logFilePath.lastPathComponent] stringByDeletingPathExtension]stringByAppendingString:[NSString stringWithFormat:@" — %@.log", [NSDate date]]]; + + NSError *error = nil; + if (![[NSFileManager defaultManager]copyItemAtPath:logFilePath toPath:destinationFilePath error:&error]) { + [[NSAlert alertWithError:error]runModal]; + return; + } + + [[NSWorkspace sharedWorkspace]activateFileViewerSelectingURLs:@[[NSURL fileURLWithPath:destinationFilePath]]]; +} + - (IBAction)quit:(id)sender { [[NSApplication sharedApplication] terminate:self]; } @@ -137,11 +177,11 @@ - (IBAction)quit:(id)sender { - (void)thunderboltObserverDeviceConnected:(KVMThunderboltObserver *)observer { NSLog(@"Thunderbolt device connected."); [self updateConnectionState:YES]; - + if ([GVUserDefaults standardUserDefaults].toggleTargetDisplayMode) { [self enableTargetDisplayMode]; } - + if ([GVUserDefaults standardUserDefaults].toggleBluetooth) { if (self.isClient) { [[KVMBluetoothController sharedController] enableBluetooth]; @@ -149,24 +189,24 @@ - (void)thunderboltObserverDeviceConnected:(KVMThunderboltObserver *)observer { [[KVMBluetoothController sharedController] disableBluetooth]; } } - + [self enableBluetooth]; } - (void)thunderboltObserverDeviceDisconnected:(KVMThunderboltObserver *)observer { NSLog(@"Thunderbolt device disconnected."); [self updateConnectionState:NO]; - + if ([GVUserDefaults standardUserDefaults].toggleTargetDisplayMode) { [self disableTargetDisplayMode]; } - + [self disableBluetooth]; } - (void)thunderboltObserver:(KVMThunderboltObserver *)observer isInitiallyConnected:(BOOL)connected { [self updateConnectionState:connected]; - + if (connected) { if ([GVUserDefaults standardUserDefaults].toggleTargetDisplayMode) { [self enableTargetDisplayMode]; @@ -174,42 +214,42 @@ - (void)thunderboltObserver:(KVMThunderboltObserver *)observer isInitiallyConnec } } - (void)enableBluetooth { - - if ([GVUserDefaults standardUserDefaults].toggleBluetooth) { - if (self.isClient) { - [[KVMBluetoothController sharedController] enableBluetooth]; - } else { - [[KVMBluetoothController sharedController] disableBluetooth]; - } + + if ([GVUserDefaults standardUserDefaults].toggleBluetooth) { + if (self.isClient) { + [[KVMBluetoothController sharedController] enableBluetooth]; + } else { + [[KVMBluetoothController sharedController] disableBluetooth]; } + } } - (void)disableBluetooth { - - if ([GVUserDefaults standardUserDefaults].toggleBluetooth) { - if (self.isClient) { - [[KVMBluetoothController sharedController] disableBluetooth]; - } else { - [[KVMBluetoothController sharedController] enableBluetooth]; - } + + if ([GVUserDefaults standardUserDefaults].toggleBluetooth) { + if (self.isClient) { + [[KVMBluetoothController sharedController] disableBluetooth]; + } else { + [[KVMBluetoothController sharedController] enableBluetooth]; } - + } + } - (void)updateConnectionState:(BOOL)connected { - - if (!self.isClient) { - self.connectionStatusMenuItem.title = [NSString stringWithFormat:@"%@: %@", [self modeString], NSLocalizedString(@"Ready to Accept Connections", comment:nil)]; - return; - } - if (connected && [self clientIsInTargetDisplayMode]) { - self.connectionStatusMenuItem.title = [NSString stringWithFormat:@"%@: %@", [self modeString], NSLocalizedString(@"Connected", comment:nil)]; - [self enableBluetooth]; - [self createPowerAssertion]; - } else { - self.connectionStatusMenuItem.title = [NSString stringWithFormat:@"%@: %@", [self modeString], NSLocalizedString(@"Not Connected", comment:nil)]; - [self disableBluetooth]; - [self disableTargetDisplayMode]; - } + + if (!self.isClient) { + self.connectionStatusMenuItem.title = [NSString stringWithFormat:@"%@: %@", [self modeString], NSLocalizedString(@"Ready to Accept Connections", comment:nil)]; + return; + } + if (connected && [self clientIsInTargetDisplayMode]) { + self.connectionStatusMenuItem.title = [NSString stringWithFormat:@"%@: %@", [self modeString], NSLocalizedString(@"Connected", comment:nil)]; + [self enableBluetooth]; + [self createPowerAssertion]; + } else { + self.connectionStatusMenuItem.title = [NSString stringWithFormat:@"%@: %@", [self modeString], NSLocalizedString(@"Not Connected", comment:nil)]; + [self disableBluetooth]; + [self disableTargetDisplayMode]; + } } #pragma mark - Helpers @@ -217,58 +257,64 @@ - (void)updateConnectionState:(BOOL)connected { - (void)enableTargetDisplayMode { NSLog(@"Attempting to enable TDM."); if (self.thunderboltObserver.isInTargetDisplayMode || self.clientIsInTargetDisplayMode) { - NSLog(@"Early return when attempting to enable TDM."); + NSLog(@"Early return when attempting to enable TDM."); return; } - + CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState); - + CGEventRef f2d = CGEventCreateKeyboardEvent(src, 0x90, true); CGEventRef f2u = CGEventCreateKeyboardEvent(src, 0x90, false); - + CGEventSetFlags(f2d, kCGEventFlagMaskSecondaryFn | kCGEventFlagMaskCommand); CGEventSetFlags(f2u, kCGEventFlagMaskSecondaryFn | kCGEventFlagMaskCommand); - + CGEventTapLocation loc = kCGHIDEventTap; CGEventPost(loc, f2d); CGEventPost(loc, f2u); - + CFRelease(f2d); CFRelease(f2u); CFRelease(src); - + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + if (!self.thunderboltObserver.isInTargetDisplayMode) { + //TDM wasn't enabled try again + [self enableTargetDisplayMode]; + } + }); } - (void)createPowerAssertion { - - if (!self.isClient) { - return; - } - if (self.sleepAssertion) {//If we already have an `_sleepAssertion` then we are already holding a power assertion. - return; - } - self.assertionType = nil; - - if ([GVUserDefaults standardUserDefaults].toggleDisableSleep) { - self.assertionType = kIOPMAssertPreventUserIdleDisplaySleep; - } else { - self.assertionType = kIOPMAssertPreventUserIdleSystemSleep; - } - CFStringRef reasonForActivity = (__bridge CFStringRef)@"In Target Display Mode"; - IOReturn success = IOPMAssertionCreateWithName(self.assertionType, kIOPMAssertionLevelOn, reasonForActivity, &_sleepAssertion); - - if (success == kIOReturnSuccess) { - NSLog(@"Created power assertion. Assertion type: %@", self.assertionType); - } else { - NSLog(@"Unable to create power assertion."); - } + + if (!self.isClient) { + return; + } + if (self.sleepAssertion) {//If we already have an `_sleepAssertion` then we are already holding a power assertion. + return; + } + self.assertionType = nil; + + if ([GVUserDefaults standardUserDefaults].toggleDisableSleep) { + self.assertionType = kIOPMAssertPreventUserIdleDisplaySleep; + } else { + self.assertionType = kIOPMAssertPreventUserIdleSystemSleep; + } + CFStringRef reasonForActivity = (__bridge CFStringRef)@"In Target Display Mode"; + IOReturn success = IOPMAssertionCreateWithName(self.assertionType, kIOPMAssertionLevelOn, reasonForActivity, &_sleepAssertion); + + if (success == kIOReturnSuccess) { + NSLog(@"Created power assertion. Assertion type: %@", self.assertionType); + } else { + NSLog(@"Unable to create power assertion."); + } } - (void)disableTargetDisplayMode { if (self.sleepAssertion != kIOPMNullAssertionID) { IOReturn success = IOPMAssertionRelease(self.sleepAssertion); - + if (success == kIOReturnSuccess) { self.sleepAssertion = kIOPMNullAssertionID; NSLog(@"Released power assertion. Assertion type: %@", self.assertionType); @@ -281,68 +327,68 @@ - (void)disableTargetDisplayMode { #pragma mark - Target Display Mode Status - (BOOL)clientIsInTargetDisplayMode { - - if (!self.isClient) { - return NO; - } - //Will have multiple objects if the the MacBook is not in clamshell mode. However, when in clamshell mode `screens` should contain only contain 1 object, this object will be the iMac's screen. - NSArray *screens = [NSScreen screens]; - - if (screens.count == 0) { - return NO; - } - - NSMutableArray *screenNumbers = [NSMutableArray new]; - for (NSScreen *screen in screens) { - if (screen.deviceDescription[@"NSScreenNumber"]) { - [screenNumbers addObject:@([screen.deviceDescription[@"NSScreenNumber"] unsignedIntValue])]; - } - } - - if (screenNumbers.count == 0) { - return NO; - } - - NSMutableArray *localizedScreenNames = [NSMutableArray new]; - - for (NSNumber *screenNumber in screenNumbers) { - - NSString *localizedScreenName = [self screenNameForDisplay:screenNumber.unsignedIntValue]; - if (localizedScreenName && localizedScreenName.length != 0) { - [localizedScreenNames addObject:localizedScreenName]; - //For testing: [localizedScreenNames addObject:@"iMac"]; - } + + if (!self.isClient) { + return NO; + } + //Will have multiple objects if the the MacBook is not in clamshell mode. However, when in clamshell mode `screens` should contain only contain 1 object, this object will be the iMac's screen. + NSArray *screens = [NSScreen screens]; + + if (screens.count == 0) { + return NO; + } + + NSMutableArray *screenNumbers = [NSMutableArray new]; + for (NSScreen *screen in screens) { + if (screen.deviceDescription[@"NSScreenNumber"]) { + [screenNumbers addObject:@([screen.deviceDescription[@"NSScreenNumber"] unsignedIntValue])]; } + } + + if (screenNumbers.count == 0) { + return NO; + } + + NSMutableArray *localizedScreenNames = [NSMutableArray new]; + + for (NSNumber *screenNumber in screenNumbers) { - if (localizedScreenNames.count == 0) { - return NO; + NSString *localizedScreenName = [self screenNameForDisplay:screenNumber.unsignedIntValue]; + if (localizedScreenName && localizedScreenName.length != 0) { + [localizedScreenNames addObject:localizedScreenName]; + //For testing: [localizedScreenNames addObject:@"iMac"]; } + } + + if (localizedScreenNames.count == 0) { + return NO; + } + + for (NSString *localizedScreenName in localizedScreenNames) { - for (NSString *localizedScreenName in localizedScreenNames) { - - if ([localizedScreenName isEqualToString:@"iMac"]) { - return YES; - break; - } + if ([localizedScreenName isEqualToString:@"iMac"]) { + return YES; + break; } - - return NO; + } + + return NO; } - (NSString *)screenNameForDisplay:(CGDirectDisplayID)displayID { - - NSString *screenName = nil; + + NSString *screenName = nil; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" - NSDictionary *deviceInfo = (__bridge NSDictionary *)IODisplayCreateInfoDictionary(CGDisplayIOServicePort(displayID), kIODisplayOnlyPreferredName); - #pragma clang diagnostic pop - NSDictionary *localizedNames = [deviceInfo objectForKey:[NSString stringWithUTF8String:kDisplayProductName]]; - - if ([localizedNames count] > 0) { - screenName = [localizedNames objectForKey:[[localizedNames allKeys] objectAtIndex:0]]; - } - - return screenName; + NSDictionary *deviceInfo = (__bridge NSDictionary *)IODisplayCreateInfoDictionary(CGDisplayIOServicePort(displayID), kIODisplayOnlyPreferredName); +#pragma clang diagnostic pop + NSDictionary *localizedNames = [deviceInfo objectForKey:[NSString stringWithUTF8String:kDisplayProductName]]; + + if ([localizedNames count] > 0) { + screenName = [localizedNames objectForKey:[[localizedNames allKeys] objectAtIndex:0]]; + } + + return screenName; } @end diff --git a/VirtualKVM/KVMThunderboltObserver.h b/VirtualKVM/KVMThunderboltObserver.h index dcbd245..3fea693 100644 --- a/VirtualKVM/KVMThunderboltObserver.h +++ b/VirtualKVM/KVMThunderboltObserver.h @@ -12,6 +12,7 @@ - (void)startObserving; - (void)stopObserving; - (BOOL)isInTargetDisplayMode; +- (NSString *)systemAssertionInfomation; @end diff --git a/VirtualKVM/KVMThunderboltObserver.m b/VirtualKVM/KVMThunderboltObserver.m index 47ed64e..a0159fb 100644 --- a/VirtualKVM/KVMThunderboltObserver.m +++ b/VirtualKVM/KVMThunderboltObserver.m @@ -1,23 +1,24 @@ #import "KVMThunderboltObserver.h" #import "KVMSystemProfiler.h" - -static NSTimeInterval const kTimeInterval = 2.0; - -typedef void (^DispatchRepeatCompletionHandler)(BOOL repeat); -typedef void (^DispatchRepeatBlock)(DispatchRepeatCompletionHandler completionHandler); -@interface KVMThunderboltObserver () +@import SBObjectiveCWrapper; +@interface KVMThunderboltObserver () @property BOOL initialized; @property BOOL macConnected; @property NSArray *systemProfilerInformation; -@property (nonatomic, copy) DispatchRepeatBlock repeatBlock; @property (nonatomic, assign) BOOL shouldRepeat; @property (nonatomic, assign, getter=isThunderboltEnabled) BOOL thunderboltEnabled; +@property (nonatomic, strong) NetworkInterfaceNotifier *networkInterfaceNotifier; + @end @implementation KVMThunderboltObserver ++ (void)initialize { + [[Uiltites shared]setupLogging]; +} + #pragma mark - Public Interface - (id)initWithDelegate:(id)delegate { @@ -25,68 +26,72 @@ - (id)initWithDelegate:(id)delegate { self.delegate = delegate; self.macConnected = NO; self.systemProfilerInformation = nil; - + self.networkInterfaceNotifier = [NetworkInterfaceNotifier new]; return self; } +- (void)dealloc { + [[NSWorkspace sharedWorkspace].notificationCenter removeObserver:self name:NSWorkspaceDidWakeNotification object:nil]; + [[NSWorkspace sharedWorkspace].notificationCenter removeObserver:self name:NSWorkspaceScreensDidWakeNotification object:nil]; +} + +#pragma mark - + +- (void)networkInterfaceNotifierDidDetectChanage { + + SBLogInfo(@"Network interface change detected"); + [self checkForThunderboltConnection]; +} + - (void)startObserving { - if (!self.repeatBlock) { - [self registerRepeatBlock]; - } + SBLogInfo(@"Start observing thunderbolt connections"); + self.networkInterfaceNotifier.delegate = self; + [self.networkInterfaceNotifier startObserving]; + [self checkForThunderboltConnection]; + [[NSWorkspace sharedWorkspace].notificationCenter addObserver:self selector:@selector(didWake) name:NSWorkspaceDidWakeNotification object:nil]; + [[NSWorkspace sharedWorkspace].notificationCenter addObserver:self selector:@selector(screenDidWake) name:NSWorkspaceScreensDidWakeNotification object:nil]; +} + +- (void)screenDidWake { + + SBLogInfo(@"Screen did wake"); + [self checkForThunderboltConnection]; +} + +- (void)didWake { + + SBLogInfo(@"Computer did wake"); + [self checkForThunderboltConnection]; } // Determines if the host has thunderbolt ports - (BOOL)isThunderboltEnabled { + + NSDictionary *profilerResponse = [self systemProfilerThunderboltInfo]; + + if (profilerResponse.count >= 1) { + NSArray *items = profilerResponse[@"_items"]; - NSDictionary *profilerResponse = [self systemProfilerThunderboltInfo]; - - if (profilerResponse.count >= 1) { - NSArray *items = profilerResponse[@"_items"]; - - if (!items || items.count == 0) { - return NO; - } - NSString *busName = items[0][@"_name"]; - - if ([busName isEqualToString:@"thunderbolt_bus"]) { - return YES; - } - return NO; + if (!items || items.count == 0) { + return NO; } + NSString *busName = items[0][@"_name"]; + if ([busName isEqualToString:@"thunderbolt_bus"]) { + return YES; + } return NO; + } + + return NO; } - -- (void)registerRepeatBlock { - - __weak typeof(self) weakSelf = self; - self.shouldRepeat = YES; - self.repeatBlock = ^(DispatchRepeatCompletionHandler completionHandler) { - typeof(self) strongSelf = weakSelf; - [strongSelf checkForThunderboltConnection]; - completionHandler(strongSelf.shouldRepeat); - }; - - [self dispatchRepeatWithTimeInterval:kTimeInterval completionHandler:self.repeatBlock]; -} - (void)stopObserving { - self.shouldRepeat = NO; - self.repeatBlock = nil; -} - -#pragma mark - Private Interface -- (void)dispatchRepeatWithTimeInterval:(NSTimeInterval)interval completionHandler:(DispatchRepeatBlock)completionHandler { - - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(interval * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - completionHandler(^(BOOL repeat) { - if (repeat) { - return [self dispatchRepeatWithTimeInterval:interval completionHandler:completionHandler]; - } - }); - }); - + SBLogInfo(@"Stop observing thunderbolt connections"); + + [[NSWorkspace sharedWorkspace].notificationCenter removeObserver:self name:NSWorkspaceDidWakeNotification object:nil]; + [[NSWorkspace sharedWorkspace].notificationCenter removeObserver:self name:NSWorkspaceScreensDidWakeNotification object:nil]; } - (void)updateSystemProfilerInformation { @@ -101,6 +106,30 @@ - (NSDictionary *)systemProfilerDisplayInfo { return self.systemProfilerInformation[0]; } +- (NSString *)systemAssertionInfomation { + + NSTask *task = [[NSTask alloc] init]; + [task setLaunchPath:@"/usr/bin/pmset"]; + [task setArguments:@[@"-g", @"assertions"]]; + + NSPipe *out = [NSPipe pipe]; + [task setStandardOutput:out]; + + @try { + [task launch]; + } @catch (NSException *exception) { + SBLogWarning(@"Caught exception: %@", exception); + return nil; + } + + NSFileHandle *read = [out fileHandleForReading]; + NSData *dataRead = [read readDataToEndOfFile]; + + NSString *string = [[NSString alloc]initWithData:dataRead encoding:NSUTF8StringEncoding]; + + return string; +} + - (NSDictionary *)systemProfilerThunderboltInfo { if (self.systemProfilerInformation == nil) { [self updateSystemProfilerInformation]; @@ -110,26 +139,31 @@ - (NSDictionary *)systemProfilerThunderboltInfo { } - (void)checkForThunderboltConnection { - [self updateSystemProfilerInformation]; - - BOOL previouslyConnected = self.macConnected; + [[NSOperationQueue mainQueue]addOperationWithBlock:^{ + + SBLogInfo(@"Checking for thuderbolt connection. \nDisplay Info: %@ \nThunderbolt Info: %@\nPower Assertion Info: %@", [self systemProfilerDisplayInfo], [self systemProfilerThunderboltInfo], [self systemAssertionInfomation]); + [self updateSystemProfilerInformation]; - if (self.isThunderboltEnabled) { + BOOL previouslyConnected = self.macConnected; + + if (self.isThunderboltEnabled) { self.macConnected = [self macConnectedViaThunderbolt]; - } else { - //Starting on macOS 10.1.4 macConnectedViaDisplayPort will return `YES` on iMac's with thunderbolt ports and therefore cause the application to always think that it is in TDM. - self.macConnected = [self macConnectedViaDisplayPort]; - } - BOOL changed = self.macConnected != previouslyConnected; - - if (changed) { - [self notifyDelegateOfConnectionChange]; - } - - if (!self.initialized) { - self.initialized = YES; - [self notifyDelegateOfInitialization]; - } + } else { + //Starting on macOS 10.1.4 macConnectedViaDisplayPort will return `YES` on iMac's with thunderbolt ports and therefore cause the application to always think that it is in TDM. + self.macConnected = [self macConnectedViaDisplayPort]; + } + + BOOL changed = self.macConnected != previouslyConnected; + + if (changed) { + [self notifyDelegateOfConnectionChange]; + } + + if (!self.initialized) { + self.initialized = YES; + [self notifyDelegateOfInitialization]; + } + }]; } - (void)notifyDelegateOfConnectionChange { @@ -154,14 +188,14 @@ - (BOOL)macConnectedViaDisplayPort { if ([self isInTargetDisplayMode]) { return YES; } - + NSDictionary *plist = [self systemProfilerDisplayInfo]; - + NSArray *gpus = plist[@"_items"]; - + for (NSDictionary *gpu in gpus) { NSArray *displays = gpu[@"spdisplays_ndrvs"]; - + for (NSDictionary *display in displays) { if ([display[@"spdisplays_connection_type"] isEqualToString:@"spdisplays_displayport_dongletype_dp"]) { if ([display[@"_spdisplays_display-vendor-id"] isEqualToString:@"610"]) { @@ -170,41 +204,27 @@ - (BOOL)macConnectedViaDisplayPort { } } } - + return NO; } - (BOOL)isInTargetDisplayMode { - NSDictionary *plist = [self systemProfilerDisplayInfo]; - - NSArray *gpus = plist[@"_items"]; - - for (NSDictionary *gpu in gpus) { - NSArray *displays = gpu[@"spdisplays_ndrvs"]; - - for (NSDictionary *display in displays) { - if ([display[@"_name"] isEqualToString:@"iMac"] && [display[@"spdisplays_builtin"] isEqualToString:@"spdisplays_yes"]) { - if (display[@"_spdisplays_displayport_device"] == nil) { - return YES; - } - } - } - } - - return NO; + NSString *assertionString = [self systemAssertionInfomation]; + //The Display Port daemon. If this isn't holding an assertion then the iMac isn't in TDM. + return [assertionString containsString:@"com.apple.dpd"]; } - (BOOL)macConnectedViaThunderbolt { NSDictionary *plist = [self systemProfilerThunderboltInfo]; - + NSArray *devices = plist[@"_items"][0][@"_items"]; - + for (NSDictionary *device in devices) { if ([device[@"vendor_id_key"] isEqualToString:@"0xA27"]) { return YES; } } - + return NO; } diff --git a/VirtualKVM/NetworkInterfaceNotifier.swift b/VirtualKVM/NetworkInterfaceNotifier.swift new file mode 100644 index 0000000..5695b30 --- /dev/null +++ b/VirtualKVM/NetworkInterfaceNotifier.swift @@ -0,0 +1,79 @@ +// +// NetworkInterfaceNotifier.swift +// VirtualKVM +// +// Created by Soneé John on 5/20/18. +// Copyright © 2018 Fast Wombat. All rights reserved. +// + +import SystemConfiguration +import Foundation + + +@objc protocol NetworkInterfaceNotifierDelegate { + func networkInterfaceNotifierDidDetectChanage() +} + +class NetworkInterfaceNotifier: NSObject { + + struct NetworkInterface { + let BSDName: String + let displayName: String + } + + override init() { + Uiltites.shared.setupLogging() + super.init() + } + + var delegate: NetworkInterfaceNotifierDelegate? + + func startObserving() { + + func callback(store: SCDynamicStore, changedKeys: CFArray, context: UnsafeMutableRawPointer?) -> Void { + guard context != nil else { return } + + let mySelf = Unmanaged.fromOpaque(context!).takeUnretainedValue() + mySelf.delegate?.networkInterfaceNotifierDidDetectChanage() + } + + OperationQueue().addOperation { + + guard Bundle.main.bundleIdentifier != nil else { + Uiltites.shared.log?.warning("Could not get bundle identifier") + return + } + + var context = SCDynamicStoreContext(version: 0, info: UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque()), retain: nil, release: nil, copyDescription: nil) + + guard let store = SCDynamicStoreCreate(nil, Bundle.main.bundleIdentifier! as CFString, callback, &context) else { + Uiltites.shared.log?.warning("Could not connect SCDynamicStoreCreate") + return + } + + var interfaces:[NetworkInterface] = [] + + for interface in SCNetworkInterfaceCopyAll() as NSArray { + if let name = SCNetworkInterfaceGetBSDName(interface as! SCNetworkInterface), + let displayName = SCNetworkInterfaceGetLocalizedDisplayName(interface as! SCNetworkInterface) { + interfaces.append(NetworkInterface(BSDName: name as String, displayName: displayName as String)) + Uiltites.shared.log?.info("Hardware Port: \(displayName) \nInterface \(name)") + } + } + + var keys = [CFString]() + + for interface in interfaces { + if interface.displayName.contains("Thunderbolt") && interface.displayName.contains("Bridge") == false { + keys.append("State:/Network/Interface/\(interface.BSDName)/LinkQuality" as CFString) + } + } + + SCDynamicStoreSetNotificationKeys(store, keys as CFArray, nil) + + let runloop = SCDynamicStoreCreateRunLoopSource(nil, store, 0) + CFRunLoopAddSource(RunLoop.current.getCFRunLoop(), runloop, CFRunLoopMode.commonModes) + RunLoop.current.run() + } + } +} diff --git a/VirtualKVM/Uiltites.swift b/VirtualKVM/Uiltites.swift new file mode 100644 index 0000000..7788973 --- /dev/null +++ b/VirtualKVM/Uiltites.swift @@ -0,0 +1,44 @@ +// +// Uiltites.swift +// VirtualKVM +// +// Created by Soneé John on 5/31/18. +// Copyright © 2018 Fast Wombat. All rights reserved. +// + +import Cocoa +import SwiftyBeaver + +@objc class Uiltites: NSObject { + + static let shared = Uiltites() + var log: SwiftyBeaver.Type? + + @objc func setupLogging() { + guard log == nil else { return } + + log = SwiftyBeaver.self + + let console = ConsoleDestination() + console.format = "$Dyyyy-MM-dd HH:mm:ss.SSS$d $C$L$c $N.$F:$l - $M" + + let file = FileDestination() + file.format = "$Dyyyy-MM-dd HH:mm:ss.SSS$d $C$L$c $N.$F:$l - $M" + + log?.addDestination(console) + log?.addDestination(file) + } + + @objc var logFilePath: String? { + guard log?.destinations.isEmpty == false else { return nil } + for desination in (log?.destinations)! { + guard desination is FileDestination else { continue } + + let fileDesination = desination as! FileDestination + return fileDesination.logFileURL?.path + } + + return nil + } + +} diff --git a/VirtualKVM/VirtualKVM-Bridging-Header.h b/VirtualKVM/VirtualKVM-Bridging-Header.h new file mode 100644 index 0000000..1b2cb5d --- /dev/null +++ b/VirtualKVM/VirtualKVM-Bridging-Header.h @@ -0,0 +1,4 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// + diff --git a/VirtualKVM/VirtualKVM-Info.plist b/VirtualKVM/VirtualKVM-Info.plist index 0a0fdc4..f95cfff 100644 --- a/VirtualKVM/VirtualKVM-Info.plist +++ b/VirtualKVM/VirtualKVM-Info.plist @@ -17,11 +17,11 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.2.3 + 1.2.4 CFBundleSignature ???? CFBundleVersion - 123 + 124 LSApplicationCategoryType public.app-category.utilities LSMinimumSystemVersion diff --git a/VirtualKVM/VirtualKVM-Prefix.pch b/VirtualKVM/VirtualKVM-Prefix.pch index 4187f19..3201039 100644 --- a/VirtualKVM/VirtualKVM-Prefix.pch +++ b/VirtualKVM/VirtualKVM-Prefix.pch @@ -7,3 +7,5 @@ #ifdef __OBJC__ #import #endif + +#import "VirtualKVM-Swift.h"