commit abcab0c509a9e1278dd9917f98c0a455d3c2dde7 Author: Ryan McGrath Date: Wed Dec 26 18:34:03 2018 -0800 Initial, holiday fun diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6f9b233 --- /dev/null +++ b/.gitignore @@ -0,0 +1,25 @@ +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## User settings +xcuserdata/ + +## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) +*.xcscmblueprint +*.xccheckout + +## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) +build/ +DerivedData/ +*.moved-aside +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 + +.DS_Store diff --git a/four.xcodeproj/project.pbxproj b/four.xcodeproj/project.pbxproj new file mode 100644 index 0000000..3ad906a --- /dev/null +++ b/four.xcodeproj/project.pbxproj @@ -0,0 +1,360 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + 2E7CEEE721CF8E1300BB164D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E7CEEE621CF8E1300BB164D /* AppDelegate.swift */; }; + 2E7CEEE921CF8E1400BB164D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2E7CEEE821CF8E1400BB164D /* Assets.xcassets */; }; + 2E7CEEF521CF904700BB164D /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E7CEEF421CF904700BB164D /* main.swift */; }; + 2E7CEEF721D0BB6500BB164D /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E7CEEF621D0BB6500BB164D /* ViewController.swift */; }; + 2E7CEEF921D0BEE500BB164D /* Icons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E7CEEF821D0BEE500BB164D /* Icons.swift */; }; + 2E7CEEFE21D0C34C00BB164D /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2E7CEEFD21D0C34C00BB164D /* NetworkExtension.framework */; }; + 2E7CEF0021D0CE7000BB164D /* JSSwitch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E7CEEFF21D0CE7000BB164D /* JSSwitch.swift */; }; + 2E7CEF0221D0D65500BB164D /* VPNManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E7CEF0121D0D65500BB164D /* VPNManager.swift */; }; + 2E7CEF0421D0E72000BB164D /* TLDList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E7CEF0321D0E72000BB164D /* TLDList.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 2E7CEEE321CF8E1300BB164D /* four.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = four.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 2E7CEEE621CF8E1300BB164D /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 2E7CEEE821CF8E1400BB164D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 2E7CEEED21CF8E1400BB164D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 2E7CEEEE21CF8E1400BB164D /* four.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = four.entitlements; sourceTree = ""; }; + 2E7CEEF421CF904700BB164D /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; + 2E7CEEF621D0BB6500BB164D /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + 2E7CEEF821D0BEE500BB164D /* Icons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Icons.swift; sourceTree = ""; }; + 2E7CEEFD21D0C34C00BB164D /* NetworkExtension.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NetworkExtension.framework; path = System/Library/Frameworks/NetworkExtension.framework; sourceTree = SDKROOT; }; + 2E7CEEFF21D0CE7000BB164D /* JSSwitch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSSwitch.swift; sourceTree = ""; }; + 2E7CEF0121D0D65500BB164D /* VPNManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNManager.swift; sourceTree = ""; }; + 2E7CEF0321D0E72000BB164D /* TLDList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TLDList.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 2E7CEEE021CF8E1300BB164D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 2E7CEEFE21D0C34C00BB164D /* NetworkExtension.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 2E7CEEDA21CF8E1300BB164D = { + isa = PBXGroup; + children = ( + 2E7CEEE521CF8E1300BB164D /* four */, + 2E7CEEE421CF8E1300BB164D /* Products */, + 2E7CEEFA21D0C33200BB164D /* Frameworks */, + ); + sourceTree = ""; + }; + 2E7CEEE421CF8E1300BB164D /* Products */ = { + isa = PBXGroup; + children = ( + 2E7CEEE321CF8E1300BB164D /* four.app */, + ); + name = Products; + sourceTree = ""; + }; + 2E7CEEE521CF8E1300BB164D /* four */ = { + isa = PBXGroup; + children = ( + 2E7CEEE621CF8E1300BB164D /* AppDelegate.swift */, + 2E7CEEE821CF8E1400BB164D /* Assets.xcassets */, + 2E7CEEED21CF8E1400BB164D /* Info.plist */, + 2E7CEEEE21CF8E1400BB164D /* four.entitlements */, + 2E7CEEF421CF904700BB164D /* main.swift */, + 2E7CEEF621D0BB6500BB164D /* ViewController.swift */, + 2E7CEEF821D0BEE500BB164D /* Icons.swift */, + 2E7CEEFF21D0CE7000BB164D /* JSSwitch.swift */, + 2E7CEF0121D0D65500BB164D /* VPNManager.swift */, + 2E7CEF0321D0E72000BB164D /* TLDList.swift */, + ); + path = four; + sourceTree = ""; + }; + 2E7CEEFA21D0C33200BB164D /* Frameworks */ = { + isa = PBXGroup; + children = ( + 2E7CEEFD21D0C34C00BB164D /* NetworkExtension.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 2E7CEEE221CF8E1300BB164D /* four */ = { + isa = PBXNativeTarget; + buildConfigurationList = 2E7CEEF121CF8E1400BB164D /* Build configuration list for PBXNativeTarget "four" */; + buildPhases = ( + 2E7CEEDF21CF8E1300BB164D /* Sources */, + 2E7CEEE021CF8E1300BB164D /* Frameworks */, + 2E7CEEE121CF8E1300BB164D /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = four; + productName = four; + productReference = 2E7CEEE321CF8E1300BB164D /* four.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 2E7CEEDB21CF8E1300BB164D /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1010; + LastUpgradeCheck = 1010; + ORGANIZATIONNAME = "Ryan McGrath"; + TargetAttributes = { + 2E7CEEE221CF8E1300BB164D = { + CreatedOnToolsVersion = 10.1; + SystemCapabilities = { + com.apple.NetworkExtensions = { + enabled = 1; + }; + com.apple.Sandbox = { + enabled = 1; + }; + com.apple.VPNLite = { + enabled = 1; + }; + }; + }; + }; + }; + buildConfigurationList = 2E7CEEDE21CF8E1300BB164D /* Build configuration list for PBXProject "four" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 2E7CEEDA21CF8E1300BB164D; + productRefGroup = 2E7CEEE421CF8E1300BB164D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 2E7CEEE221CF8E1300BB164D /* four */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 2E7CEEE121CF8E1300BB164D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 2E7CEEE921CF8E1400BB164D /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 2E7CEEDF21CF8E1300BB164D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 2E7CEEF721D0BB6500BB164D /* ViewController.swift in Sources */, + 2E7CEF0021D0CE7000BB164D /* JSSwitch.swift in Sources */, + 2E7CEEF521CF904700BB164D /* main.swift in Sources */, + 2E7CEEF921D0BEE500BB164D /* Icons.swift in Sources */, + 2E7CEF0221D0D65500BB164D /* VPNManager.swift in Sources */, + 2E7CEEE721CF8E1300BB164D /* AppDelegate.swift in Sources */, + 2E7CEF0421D0E72000BB164D /* TLDList.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 2E7CEEEF21CF8E1400BB164D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + 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_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_IDENTITY = "Mac Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + 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 = 10.14; + 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; + }; + 2E7CEEF021CF8E1400BB164D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + 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_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_IDENTITY = "Mac Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + 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 = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 2E7CEEF221CF8E1400BB164D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ENTITLEMENTS = four/four.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = 8JNDBG9U79; + INFOPLIST_FILE = four/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.rymc.four; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 4.2; + }; + name = Debug; + }; + 2E7CEEF321CF8E1400BB164D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ENTITLEMENTS = four/four.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = 8JNDBG9U79; + INFOPLIST_FILE = four/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.rymc.four; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 4.2; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 2E7CEEDE21CF8E1300BB164D /* Build configuration list for PBXProject "four" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 2E7CEEEF21CF8E1400BB164D /* Debug */, + 2E7CEEF021CF8E1400BB164D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 2E7CEEF121CF8E1400BB164D /* Build configuration list for PBXNativeTarget "four" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 2E7CEEF221CF8E1400BB164D /* Debug */, + 2E7CEEF321CF8E1400BB164D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 2E7CEEDB21CF8E1300BB164D /* Project object */; +} diff --git a/four.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/four.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..df4a603 --- /dev/null +++ b/four.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/four.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/four.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/four.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/four/AppDelegate.swift b/four/AppDelegate.swift new file mode 100644 index 0000000..2fa1c86 --- /dev/null +++ b/four/AppDelegate.swift @@ -0,0 +1,44 @@ +// +// AppDelegate.swift +// four +// +// Created by Ryan McGrath on 12/23/18. +// Copyright © 2018 Ryan McGrath. All rights reserved. +// + +import Cocoa + +class AppDelegate: NSObject, NSApplicationDelegate { + lazy var popover: NSPopover = { + return NSPopover() + }() + + lazy var statusItem: NSStatusItem = { + return NSStatusBar.system.statusItem(withLength: NSStatusItem.squareLength) + }() + + func applicationDidFinishLaunching(_ aNotification: Notification) { + popover.behavior = .transient + popover.appearance = NSAppearance.current + popover.contentSize = CGSize(width: 300.0, height: 300.0) + popover.contentViewController = ViewController() + + let statusBarIcon = Icons.statusBarIcon() + statusBarIcon.isTemplate = true + statusItem.button?.image = statusBarIcon + statusItem.button?.alternateImage = statusBarIcon + statusItem.button?.action = #selector(togglePopover) + } + + func applicationWillTerminate(_ aNotification: Notification) { + } + + @objc + func togglePopover(sender: Any?) { + if(popover.isShown) { + popover.performClose(sender) + } else { + popover.show(relativeTo: statusItem.button!.bounds, of: statusItem.button!, preferredEdge: .minY) + } + } +} diff --git a/four/Assets.xcassets/AppIcon.appiconset/Contents.json b/four/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..2db2b1c --- /dev/null +++ b/four/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,58 @@ +{ + "images" : [ + { + "idiom" : "mac", + "size" : "16x16", + "scale" : "1x" + }, + { + "idiom" : "mac", + "size" : "16x16", + "scale" : "2x" + }, + { + "idiom" : "mac", + "size" : "32x32", + "scale" : "1x" + }, + { + "idiom" : "mac", + "size" : "32x32", + "scale" : "2x" + }, + { + "idiom" : "mac", + "size" : "128x128", + "scale" : "1x" + }, + { + "idiom" : "mac", + "size" : "128x128", + "scale" : "2x" + }, + { + "idiom" : "mac", + "size" : "256x256", + "scale" : "1x" + }, + { + "idiom" : "mac", + "size" : "256x256", + "scale" : "2x" + }, + { + "idiom" : "mac", + "size" : "512x512", + "scale" : "1x" + }, + { + "idiom" : "mac", + "size" : "512x512", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/four/Assets.xcassets/Contents.json b/four/Assets.xcassets/Contents.json new file mode 100644 index 0000000..da4a164 --- /dev/null +++ b/four/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/four/Icons.swift b/four/Icons.swift new file mode 100644 index 0000000..f450a66 --- /dev/null +++ b/four/Icons.swift @@ -0,0 +1,112 @@ +// +// Icons.swift +// four +// +// Created by Ryan McGrath on 12/23/18. +// Copyright © 2018 Ryan McGrath. All rights reserved. +// + +import Foundation +import Cocoa + +struct Icons { + public static func statusBarIcon(frame targetFrame: NSRect = NSRect(x: 0, y: 0, width: 32, height: 32), resizing: ResizingBehavior = .aspectFit) -> NSImage { + let resizedFrame: NSRect = resizing.apply(rect: NSRect(x: 0, y: 0, width: 64, height: 64), target: targetFrame) + return NSImage(size: resizedFrame.size, flipped: false, drawingHandler: { (destRect: NSRect) -> Bool in + let context = NSGraphicsContext.current?.cgContext + NSGraphicsContext.saveGraphicsState() + context?.translateBy(x: resizedFrame.minX, y: resizedFrame.minY) + context?.scaleBy(x: resizedFrame.size.width / 64.0, y: resizedFrame.size.height / 64.0) + + //// Color Declarations + let fillColor = NSColor(red: 1, green: 1, blue: 1, alpha: 1) + + //// Bezier Drawing + let bezierPath = NSBezierPath() + bezierPath.move(to: NSPoint(x: 43.62, y: 16)) + bezierPath.line(to: NSPoint(x: 18.53, y: 16)) + bezierPath.line(to: NSPoint(x: 18.52, y: 16)) + bezierPath.curve(to: NSPoint(x: 9.01, y: 25.46), controlPoint1: NSPoint(x: 13.27, y: 16), controlPoint2: NSPoint(x: 9.01, y: 20.24)) + bezierPath.curve(to: NSPoint(x: 15.11, y: 34.29), controlPoint1: NSPoint(x: 9.01, y: 29.38), controlPoint2: NSPoint(x: 11.44, y: 32.89)) + bezierPath.line(to: NSPoint(x: 15.08, y: 34.24)) + bezierPath.curve(to: NSPoint(x: 23.86, y: 42.68), controlPoint1: NSPoint(x: 15.16, y: 38.98), controlPoint2: NSPoint(x: 19.09, y: 42.76)) + bezierPath.curve(to: NSPoint(x: 25.51, y: 42.49), controlPoint1: NSPoint(x: 24.41, y: 42.67), controlPoint2: NSPoint(x: 24.97, y: 42.6)) + bezierPath.line(to: NSPoint(x: 25.56, y: 42.52)) + bezierPath.curve(to: NSPoint(x: 42.59, y: 44.3), controlPoint1: NSPoint(x: 29.77, y: 47.68), controlPoint2: NSPoint(x: 37.39, y: 48.48)) + bezierPath.curve(to: NSPoint(x: 47.03, y: 35.99), controlPoint1: NSPoint(x: 45.14, y: 42.24), controlPoint2: NSPoint(x: 46.74, y: 39.24)) + bezierPath.line(to: NSPoint(x: 47, y: 36.08)) + bezierPath.curve(to: NSPoint(x: 53.43, y: 22.96), controlPoint1: NSPoint(x: 52.42, y: 34.22), controlPoint2: NSPoint(x: 55.3, y: 28.35)) + bezierPath.curve(to: NSPoint(x: 43.62, y: 16), controlPoint1: NSPoint(x: 51.99, y: 18.8), controlPoint2: NSPoint(x: 48.05, y: 16)) + bezierPath.line(to: NSPoint(x: 43.62, y: 16)) + bezierPath.close() + bezierPath.move(to: NSPoint(x: 44.84, y: 33.2)) + bezierPath.line(to: NSPoint(x: 44.83, y: 33.21)) + bezierPath.curve(to: NSPoint(x: 43.67, y: 34.68), controlPoint1: NSPoint(x: 44.19, y: 33.43), controlPoint2: NSPoint(x: 43.73, y: 34)) + bezierPath.line(to: NSPoint(x: 43.59, y: 35.6)) + bezierPath.line(to: NSPoint(x: 43.59, y: 35.6)) + bezierPath.curve(to: NSPoint(x: 35.03, y: 43.54), controlPoint1: NSPoint(x: 43.2, y: 40.03), controlPoint2: NSPoint(x: 39.5, y: 43.46)) + bezierPath.curve(to: NSPoint(x: 27.55, y: 39.51), controlPoint1: NSPoint(x: 31.04, y: 43.58), controlPoint2: NSPoint(x: 28.92, y: 41.23)) + bezierPath.line(to: NSPoint(x: 27.54, y: 39.5)) + bezierPath.curve(to: NSPoint(x: 25.82, y: 38.91), controlPoint1: NSPoint(x: 27.13, y: 38.99), controlPoint2: NSPoint(x: 26.46, y: 38.76)) + bezierPath.line(to: NSPoint(x: 25.83, y: 38.9)) + bezierPath.curve(to: NSPoint(x: 23.49, y: 39.23), controlPoint1: NSPoint(x: 25.07, y: 39.12), controlPoint2: NSPoint(x: 24.29, y: 39.23)) + bezierPath.line(to: NSPoint(x: 23.55, y: 39.23)) + bezierPath.curve(to: NSPoint(x: 18.53, y: 34.16), controlPoint1: NSPoint(x: 20.79, y: 39.14), controlPoint2: NSPoint(x: 18.58, y: 36.91)) + bezierPath.line(to: NSPoint(x: 18.51, y: 33.06)) + bezierPath.line(to: NSPoint(x: 18.51, y: 33.06)) + bezierPath.curve(to: NSPoint(x: 17.43, y: 31.51), controlPoint1: NSPoint(x: 18.5, y: 32.38), controlPoint2: NSPoint(x: 18.07, y: 31.76)) + bezierPath.curve(to: NSPoint(x: 12.48, y: 25.17), controlPoint1: NSPoint(x: 15.18, y: 30.63), controlPoint2: NSPoint(x: 12.33, y: 29.21)) + bezierPath.line(to: NSPoint(x: 12.48, y: 25.13)) + bezierPath.curve(to: NSPoint(x: 18.61, y: 19.44), controlPoint1: NSPoint(x: 12.65, y: 21.9), controlPoint2: NSPoint(x: 15.36, y: 19.39)) + bezierPath.line(to: NSPoint(x: 43.53, y: 19.44)) + bezierPath.line(to: NSPoint(x: 43.57, y: 19.44)) + bezierPath.curve(to: NSPoint(x: 50.54, y: 26.26), controlPoint1: NSPoint(x: 47.37, y: 19.44), controlPoint2: NSPoint(x: 50.47, y: 22.47)) + bezierPath.curve(to: NSPoint(x: 44.84, y: 33.2), controlPoint1: NSPoint(x: 50.57, y: 30.89), controlPoint2: NSPoint(x: 46.96, y: 32.48)) + bezierPath.close() + bezierPath.windingRule = .evenOdd + fillColor.setFill() + bezierPath.fill() + + NSGraphicsContext.restoreGraphicsState() + return true + }) + } + + public enum ResizingBehavior: Int { + case aspectFit /// The content is proportionally resized to fit into the target rectangle. + case aspectFill /// The content is proportionally resized to completely fill the target rectangle. + case stretch /// The content is stretched to match the entire target rectangle. + case center /// The content is centered in the target rectangle, but it is NOT resized. + + public func apply(rect: NSRect, target: NSRect) -> NSRect { + if rect == target || target == NSRect.zero { + return rect + } + + var scales = NSSize.zero + scales.width = abs(target.width / rect.width) + scales.height = abs(target.height / rect.height) + + switch self { + case .aspectFit: + scales.width = min(scales.width, scales.height) + scales.height = scales.width + case .aspectFill: + scales.width = max(scales.width, scales.height) + scales.height = scales.width + case .stretch: + break + case .center: + scales.width = 1 + scales.height = 1 + } + + var result = rect.standardized + result.size.width *= scales.width + result.size.height *= scales.height + result.origin.x = target.minX + (target.width - result.width) / 2 + result.origin.y = target.minY + (target.height - result.height) / 2 + return result + } + } +} diff --git a/four/Info.plist b/four/Info.plist new file mode 100644 index 0000000..a84aba2 --- /dev/null +++ b/four/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + Copyright © 2018 Ryan McGrath. All rights reserved. + LSUIElement + + NSPrincipalClass + NSApplication + + diff --git a/four/JSSwitch.swift b/four/JSSwitch.swift new file mode 100644 index 0000000..0efc66c --- /dev/null +++ b/four/JSSwitch.swift @@ -0,0 +1,181 @@ +// +// JSSwitch.swift +// JSSwitch +// +// Created by Julien Sagot on 29/05/16. +// Modified slightly by Ryan McGrath on or about 12/24/2018. +// Copyright © 2016 Julien Sagot. All rights reserved. +// +import AppKit + +public class JSSwitch: NSControl { + // MARK: - Properties + private var pressed = false + private let backgroundLayer = CALayer() + private let knobContainer = CALayer() + private let knobLayer = CALayer() + private let knobShadows = (smallStroke: CALayer(), smallShadow: CALayer(), mediumShadow: CALayer(), bigShadow: CALayer()) + + // MARK: Computed + public override var wantsUpdateLayer: Bool { return true } + public override var intrinsicContentSize: NSSize { + return CGSize(width: 52, height: 32) + } + + private var scaleFactor: CGFloat { + return ceil(frame.size.height / 62) // Hardcoded base height + } + + public var tintColor = NSColor(deviceRed: 76/255, green: 217/255, blue: 100/255, alpha: 1.0) { + didSet { needsDisplay = true } + } + public var on = false { + didSet { needsDisplay = true } + } + + // MARK: - Initializers + public override init(frame: CGRect) { + super.init(frame: frame) + setupLayers() + } + + required public init?(coder: NSCoder) { + super.init(coder: coder) + setupLayers() + } + + // MARK: - Layers Setup + private func setupLayers() { + wantsLayer = true + layer?.masksToBounds = false + layerContentsRedrawPolicy = .onSetNeedsDisplay + + // Background + setupBackgroundLayer() + layer?.addSublayer(backgroundLayer) + + // Knob + setupKnobLayers() + layer?.addSublayer(knobContainer) + } + + // MARK: Background Layer + private func setupBackgroundLayer() { + backgroundLayer.frame = bounds + backgroundLayer.autoresizingMask = [.layerWidthSizable, .layerHeightSizable] + } + + // MARK: Knob + private func setupKnobLayers() { + setupKnobContainerLayer() + setupKnobLayer() + setupKnobLayerShadows() + knobContainer.addSublayer(knobLayer) + knobContainer.insertSublayer(knobShadows.smallStroke, below: knobLayer) + knobContainer.insertSublayer(knobShadows.smallShadow, below: knobShadows.smallStroke) + knobContainer.insertSublayer(knobShadows.mediumShadow, below: knobShadows.smallShadow) + knobContainer.insertSublayer(knobShadows.bigShadow, below: knobShadows.mediumShadow) + } + + private func setupKnobContainerLayer() { + knobContainer.frame = knobFrameForState(on: false, pressed: false) + } + + private func setupKnobLayer() { + knobLayer.autoresizingMask = [.layerWidthSizable] + knobLayer.backgroundColor = NSColor.white.cgColor + knobLayer.frame = knobContainer.bounds + knobLayer.cornerRadius = ceil(knobContainer.bounds.height / 2) + } + + private func setupKnobLayerShadows() { + let effectScale = scaleFactor + // Small Stroke + let smallStroke = knobShadows.smallStroke + smallStroke.frame = knobLayer.frame.insetBy(dx: -1, dy: -1) + smallStroke.autoresizingMask = [.layerWidthSizable] + smallStroke.backgroundColor = NSColor.black.withAlphaComponent(0.06).cgColor + smallStroke.cornerRadius = ceil(smallStroke.bounds.height / 2) + + let smallShadow = knobShadows.smallShadow + smallShadow.frame = knobLayer.frame.insetBy(dx: 2, dy: 2) + smallShadow.autoresizingMask = [.layerWidthSizable] + smallShadow.cornerRadius = ceil(smallShadow.bounds.height / 2) + smallShadow.backgroundColor = NSColor.red.cgColor + smallShadow.shadowColor = NSColor.black.cgColor + smallShadow.shadowOffset = CGSize(width: 0, height: -3 * effectScale) + smallShadow.shadowOpacity = 0.12 + smallShadow.shadowRadius = 2.0 * effectScale + + let mediumShadow = knobShadows.mediumShadow + mediumShadow.frame = smallShadow.frame + mediumShadow.autoresizingMask = [.layerWidthSizable] + mediumShadow.cornerRadius = smallShadow.cornerRadius + mediumShadow.backgroundColor = NSColor.red.cgColor + mediumShadow.shadowColor = NSColor.black.cgColor + mediumShadow.shadowOffset = CGSize(width: 0, height: -9 * effectScale) + mediumShadow.shadowOpacity = 0.16 + mediumShadow.shadowRadius = 6.0 * effectScale + + let bigShadow = knobShadows.bigShadow + bigShadow.frame = smallShadow.frame + bigShadow.autoresizingMask = [.layerWidthSizable] + bigShadow.cornerRadius = smallShadow.cornerRadius + bigShadow.backgroundColor = NSColor.red.cgColor + bigShadow.shadowColor = NSColor.black.cgColor + bigShadow.shadowOffset = CGSize(width: 0, height: -9 * effectScale) + bigShadow.shadowOpacity = 0.06 + bigShadow.shadowRadius = 0.5 * effectScale + } + + // MARK: - Drawing + public override func updateLayer() { + // Background + backgroundLayer.cornerRadius = ceil(bounds.height / 2) + backgroundLayer.borderWidth = on ? ceil(bounds.height) : 3.0 * scaleFactor + backgroundLayer.borderColor = on ? tintColor.cgColor : NSColor.black.withAlphaComponent(0.09).cgColor + + // Knob + knobContainer.frame = knobFrameForState(on: on, pressed: pressed) + knobLayer.cornerRadius = ceil(knobContainer.bounds.height / 2) + } + + // MARK: - Helpers + private func knobFrameForState(on: Bool, pressed: Bool) -> CGRect { + let borderWidth = 3.0 * scaleFactor + var origin: CGPoint + var size: CGSize { + if pressed { + return CGSize( + width: ceil(bounds.width * 0.69) - (2 * borderWidth), + height: bounds.height - (2 * borderWidth) + ) + } + return CGSize(width: bounds.height - (2 * borderWidth), height: bounds.height - (2 * borderWidth)) + } + + if on { + origin = CGPoint(x: bounds.width - size.width - borderWidth, y: borderWidth) + } else { + origin = CGPoint(x: borderWidth, y: borderWidth) + } + return CGRect(origin: origin, size: size) + } + + // MARK: - Events + public override func mouseDown(with theEvent: NSEvent) { + super.mouseDown(with: theEvent) + pressed = true + needsDisplay = true + } + + public override func mouseUp(with theEvent: NSEvent) { + super.mouseUp(with: theEvent) + pressed = false + on = !on + + if(action != nil && target != nil) { + sendAction(action, to: target) + } + } +} diff --git a/four/TLDList.swift b/four/TLDList.swift new file mode 100644 index 0000000..f3a5344 --- /dev/null +++ b/four/TLDList.swift @@ -0,0 +1,1551 @@ +// +// TLDList.swift +// four +// +// Created by Ryan McGrath on 12/24/18. +// Copyright © 2018 Ryan McGrath. All rights reserved. +// + +let TLDS = [ + "*.aaa", + "*.aarp", + "*.abarth", + "*.abb", + "*.abbott", + "*.abbvie", + "*.abc", + "*.able", + "*.abogado", + "*.abudhabi", + "*.ac", + "*.academy", + "*.accenture", + "*.accountant", + "*.accountants", + "*.aco", + "*.active", + "*.actor", + "*.ad", + "*.adac", + "*.ads", + "*.adult", + "*.ae", + "*.aeg", + "*.aero", + "*.aetna", + "*.af", + "*.afamilycompany", + "*.afl", + "*.africa", + "*.ag", + "*.agakhan", + "*.agency", + "*.ai", + "*.aig", + "*.aigo", + "*.airbus", + "*.airforce", + "*.airtel", + "*.akdn", + "*.al", + "*.alfaromeo", + "*.alibaba", + "*.alipay", + "*.allfinanz", + "*.allstate", + "*.ally", + "*.alsace", + "*.alstom", + "*.am", + "*.americanexpress", + "*.americanfamily", + "*.amex", + "*.amfam", + "*.amica", + "*.amsterdam", + "*.analytics", + "*.android", + "*.anquan", + "*.anz", + "*.ao", + "*.aol", + "*.apartments", + "*.app", + "*.apple", + "*.aq", + "*.aquarelle", + "*.ar", + "*.arab", + "*.aramco", + "*.archi", + "*.army", + "*.arpa", + "*.art", + "*.arte", + "*.as", + "*.asda", + "*.asia", + "*.associates", + "*.at", + "*.athleta", + "*.attorney", + "*.au", + "*.auction", + "*.audi", + "*.audible", + "*.audio", + "*.auspost", + "*.author", + "*.auto", + "*.autos", + "*.avianca", + "*.aw", + "*.aws", + "*.ax", + "*.axa", + "*.az", + "*.azure", + "*.ba", + "*.baby", + "*.baidu", + "*.banamex", + "*.bananarepublic", + "*.band", + "*.bank", + "*.bar", + "*.barcelona", + "*.barclaycard", + "*.barclays", + "*.barefoot", + "*.bargains", + "*.baseball", + "*.basketball", + "*.bauhaus", + "*.bayern", + "*.bb", + "*.bbc", + "*.bbt", + "*.bbva", + "*.bcg", + "*.bcn", + "*.bd", + "*.be", + "*.beats", + "*.beauty", + "*.beer", + "*.bentley", + "*.berlin", + "*.best", + "*.bestbuy", + "*.bet", + "*.bf", + "*.bg", + "*.bh", + "*.bharti", + "*.bi", + "*.bible", + "*.bid", + "*.bike", + "*.bing", + "*.bingo", + "*.bio", + "*.biz", + "*.bj", + "*.black", + "*.blackfriday", + "*.blanco", + "*.blockbuster", + "*.blog", + "*.bloomberg", + "*.blue", + "*.bm", + "*.bms", + "*.bmw", + "*.bn", + "*.bnl", + "*.bnpparibas", + "*.bo", + "*.boats", + "*.boehringer", + "*.bofa", + "*.bom", + "*.bond", + "*.boo", + "*.book", + "*.booking", + "*.bosch", + "*.bostik", + "*.boston", + "*.bot", + "*.boutique", + "*.box", + "*.br", + "*.bradesco", + "*.bridgestone", + "*.broadway", + "*.broker", + "*.brother", + "*.brussels", + "*.bs", + "*.bt", + "*.budapest", + "*.bugatti", + "*.build", + "*.builders", + "*.business", + "*.buy", + "*.buzz", + "*.bv", + "*.bw", + "*.by", + "*.bz", + "*.bzh", + "*.ca", + "*.cab", + "*.cafe", + "*.cal", + "*.call", + "*.calvinklein", + "*.cam", + "*.camera", + "*.camp", + "*.cancerresearch", + "*.canon", + "*.capetown", + "*.capital", + "*.capitalone", + "*.car", + "*.caravan", + "*.cards", + "*.care", + "*.career", + "*.careers", + "*.cars", + "*.cartier", + "*.casa", + "*.case", + "*.caseih", + "*.cash", + "*.casino", + "*.cat", + "*.catering", + "*.catholic", + "*.cba", + "*.cbn", + "*.cbre", + "*.cbs", + "*.cc", + "*.cd", + "*.ceb", + "*.center", + "*.ceo", + "*.cern", + "*.cf", + "*.cfa", + "*.cfd", + "*.cg", + "*.ch", + "*.chanel", + "*.channel", + "*.chase", + "*.chat", + "*.cheap", + "*.chintai", + "*.christmas", + "*.chrome", + "*.chrysler", + "*.church", + "*.ci", + "*.cipriani", + "*.circle", + "*.cisco", + "*.citadel", + "*.citi", + "*.citic", + "*.city", + "*.cityeats", + "*.ck", + "*.cl", + "*.claims", + "*.cleaning", + "*.click", + "*.clinic", + "*.clinique", + "*.clothing", + "*.cloud", + "*.club", + "*.clubmed", + "*.cm", + "*.cn", + "*.co", + "*.coach", + "*.codes", + "*.coffee", + "*.college", + "*.cologne", + "*.com", + "*.comcast", + "*.commbank", + "*.community", + "*.company", + "*.compare", + "*.computer", + "*.comsec", + "*.condos", + "*.construction", + "*.consulting", + "*.contact", + "*.contractors", + "*.cooking", + "*.cookingchannel", + "*.cool", + "*.coop", + "*.corsica", + "*.country", + "*.coupon", + "*.coupons", + "*.courses", + "*.cr", + "*.credit", + "*.creditcard", + "*.creditunion", + "*.cricket", + "*.crown", + "*.crs", + "*.cruise", + "*.cruises", + "*.csc", + "*.cu", + "*.cuisinella", + "*.cv", + "*.cw", + "*.cx", + "*.cy", + "*.cymru", + "*.cyou", + "*.cz", + "*.dabur", + "*.dad", + "*.dance", + "*.data", + "*.date", + "*.dating", + "*.datsun", + "*.day", + "*.dclk", + "*.dds", + "*.de", + "*.deal", + "*.dealer", + "*.deals", + "*.degree", + "*.delivery", + "*.dell", + "*.deloitte", + "*.delta", + "*.democrat", + "*.dental", + "*.dentist", + "*.desi", + "*.design", + "*.dev", + "*.dhl", + "*.diamonds", + "*.diet", + "*.digital", + "*.direct", + "*.directory", + "*.discount", + "*.discover", + "*.dish", + "*.diy", + "*.dj", + "*.dk", + "*.dm", + "*.dnp", + "*.do", + "*.docs", + "*.doctor", + "*.dodge", + "*.dog", + "*.doha", + "*.domains", + "*.dot", + "*.download", + "*.drive", + "*.dtv", + "*.dubai", + "*.duck", + "*.dunlop", + "*.duns", + "*.dupont", + "*.durban", + "*.dvag", + "*.dvr", + "*.dz", + "*.earth", + "*.eat", + "*.ec", + "*.eco", + "*.edeka", + "*.edu", + "*.education", + "*.ee", + "*.eg", + "*.email", + "*.emerck", + "*.energy", + "*.engineer", + "*.engineering", + "*.enterprises", + "*.epost", + "*.epson", + "*.equipment", + "*.er", + "*.ericsson", + "*.erni", + "*.es", + "*.esq", + "*.estate", + "*.esurance", + "*.et", + "*.etisalat", + "*.eu", + "*.eurovision", + "*.eus", + "*.events", + "*.everbank", + "*.exchange", + "*.expert", + "*.exposed", + "*.express", + "*.extraspace", + "*.fage", + "*.fail", + "*.fairwinds", + "*.faith", + "*.family", + "*.fan", + "*.fans", + "*.farm", + "*.farmers", + "*.fashion", + "*.fast", + "*.fedex", + "*.feedback", + "*.ferrari", + "*.ferrero", + "*.fi", + "*.fiat", + "*.fidelity", + "*.fido", + "*.film", + "*.final", + "*.finance", + "*.financial", + "*.fire", + "*.firestone", + "*.firmdale", + "*.fish", + "*.fishing", + "*.fit", + "*.fitness", + "*.fj", + "*.fk", + "*.flickr", + "*.flights", + "*.flir", + "*.florist", + "*.flowers", + "*.fly", + "*.fm", + "*.fo", + "*.foo", + "*.food", + "*.foodnetwork", + "*.football", + "*.ford", + "*.forex", + "*.forsale", + "*.forum", + "*.foundation", + "*.fox", + "*.fr", + "*.free", + "*.fresenius", + "*.frl", + "*.frogans", + "*.frontdoor", + "*.frontier", + "*.ftr", + "*.fujitsu", + "*.fujixerox", + "*.fun", + "*.fund", + "*.furniture", + "*.futbol", + "*.fyi", + "*.ga", + "*.gal", + "*.gallery", + "*.gallo", + "*.gallup", + "*.game", + "*.games", + "*.gap", + "*.garden", + "*.gb", + "*.gbiz", + "*.gd", + "*.gdn", + "*.ge", + "*.gea", + "*.gent", + "*.genting", + "*.george", + "*.gf", + "*.gg", + "*.ggee", + "*.gh", + "*.gi", + "*.gift", + "*.gifts", + "*.gives", + "*.giving", + "*.gl", + "*.glade", + "*.glass", + "*.gle", + "*.global", + "*.globo", + "*.gm", + "*.gmail", + "*.gmbh", + "*.gmo", + "*.gmx", + "*.gn", + "*.godaddy", + "*.gold", + "*.goldpoint", + "*.golf", + "*.goo", + "*.goodhands", + "*.goodyear", + "*.goog", + "*.google", + "*.gop", + "*.got", + "*.gov", + "*.gp", + "*.gq", + "*.gr", + "*.grainger", + "*.graphics", + "*.gratis", + "*.green", + "*.gripe", + "*.grocery", + "*.group", + "*.gs", + "*.gt", + "*.gu", + "*.guardian", + "*.gucci", + "*.guge", + "*.guide", + "*.guitars", + "*.guru", + "*.gw", + "*.gy", + "*.hair", + "*.hamburg", + "*.hangout", + "*.haus", + "*.hbo", + "*.hdfc", + "*.hdfcbank", + "*.health", + "*.healthcare", + "*.help", + "*.helsinki", + "*.here", + "*.hermes", + "*.hgtv", + "*.hiphop", + "*.hisamitsu", + "*.hitachi", + "*.hiv", + "*.hk", + "*.hkt", + "*.hm", + "*.hn", + "*.hockey", + "*.holdings", + "*.holiday", + "*.homedepot", + "*.homegoods", + "*.homes", + "*.homesense", + "*.honda", + "*.honeywell", + "*.horse", + "*.hospital", + "*.host", + "*.hosting", + "*.hot", + "*.hoteles", + "*.hotels", + "*.hotmail", + "*.house", + "*.how", + "*.hr", + "*.hsbc", + "*.ht", + "*.hu", + "*.hughes", + "*.hyatt", + "*.hyundai", + "*.ibm", + "*.icbc", + "*.ice", + "*.icu", + "*.id", + "*.ie", + "*.ieee", + "*.ifm", + "*.ikano", + "*.il", + "*.im", + "*.imamat", + "*.imdb", + "*.immo", + "*.immobilien", + "*.in", + "*.industries", + "*.infiniti", + "*.info", + "*.ing", + "*.ink", + "*.institute", + "*.insurance", + "*.insure", + "*.int", + "*.intel", + "*.international", + "*.intuit", + "*.investments", + "*.io", + "*.ipiranga", + "*.iq", + "*.ir", + "*.irish", + "*.is", + "*.iselect", + "*.ismaili", + "*.ist", + "*.istanbul", + "*.it", + "*.itau", + "*.itv", + "*.iveco", + "*.iwc", + "*.jaguar", + "*.java", + "*.jcb", + "*.jcp", + "*.je", + "*.jeep", + "*.jetzt", + "*.jewelry", + "*.jio", + "*.jlc", + "*.jll", + "*.jm", + "*.jmp", + "*.jnj", + "*.jo", + "*.jobs", + "*.joburg", + "*.jot", + "*.joy", + "*.jp", + "*.jpmorgan", + "*.jprs", + "*.juegos", + "*.juniper", + "*.kaufen", + "*.kddi", + "*.ke", + "*.kerryhotels", + "*.kerrylogistics", + "*.kerryproperties", + "*.kfh", + "*.kg", + "*.kh", + "*.ki", + "*.kia", + "*.kim", + "*.kinder", + "*.kindle", + "*.kitchen", + "*.kiwi", + "*.km", + "*.kn", + "*.koeln", + "*.komatsu", + "*.kosher", + "*.kp", + "*.kpmg", + "*.kpn", + "*.kr", + "*.krd", + "*.kred", + "*.kuokgroup", + "*.kw", + "*.ky", + "*.kyoto", + "*.kz", + "*.la", + "*.lacaixa", + "*.ladbrokes", + "*.lamborghini", + "*.lamer", + "*.lancaster", + "*.lancia", + "*.lancome", + "*.land", + "*.landrover", + "*.lanxess", + "*.lasalle", + "*.lat", + "*.latino", + "*.latrobe", + "*.law", + "*.lawyer", + "*.lb", + "*.lc", + "*.lds", + "*.lease", + "*.leclerc", + "*.lefrak", + "*.legal", + "*.lego", + "*.lexus", + "*.lgbt", + "*.li", + "*.liaison", + "*.lidl", + "*.life", + "*.lifeinsurance", + "*.lifestyle", + "*.lighting", + "*.like", + "*.lilly", + "*.limited", + "*.limo", + "*.lincoln", + "*.linde", + "*.link", + "*.lipsy", + "*.live", + "*.living", + "*.lixil", + "*.lk", + "*.llc", + "*.loan", + "*.loans", + "*.locker", + "*.locus", + "*.loft", + "*.lol", + "*.london", + "*.lotte", + "*.lotto", + "*.love", + "*.lpl", + "*.lplfinancial", + "*.lr", + "*.ls", + "*.lt", + "*.ltd", + "*.ltda", + "*.lu", + "*.lundbeck", + "*.lupin", + "*.luxe", + "*.luxury", + "*.lv", + "*.ly", + "*.ma", + "*.macys", + "*.madrid", + "*.maif", + "*.maison", + "*.makeup", + "*.man", + "*.management", + "*.mango", + "*.map", + "*.market", + "*.marketing", + "*.markets", + "*.marriott", + "*.marshalls", + "*.maserati", + "*.mattel", + "*.mba", + "*.mc", + "*.mckinsey", + "*.md", + "*.me", + "*.med", + "*.media", + "*.meet", + "*.melbourne", + "*.meme", + "*.memorial", + "*.men", + "*.menu", + "*.merckmsd", + "*.metlife", + "*.mg", + "*.mh", + "*.miami", + "*.microsoft", + "*.mil", + "*.mini", + "*.mint", + "*.mit", + "*.mitsubishi", + "*.mk", + "*.ml", + "*.mlb", + "*.mls", + "*.mm", + "*.mma", + "*.mn", + "*.mo", + "*.mobi", + "*.mobile", + "*.mobily", + "*.moda", + "*.moe", + "*.moi", + "*.mom", + "*.monash", + "*.money", + "*.monster", + "*.mopar", + "*.mormon", + "*.mortgage", + "*.moscow", + "*.moto", + "*.motorcycles", + "*.mov", + "*.movie", + "*.movistar", + "*.mp", + "*.mq", + "*.mr", + "*.ms", + "*.msd", + "*.mt", + "*.mtn", + "*.mtr", + "*.mu", + "*.museum", + "*.mutual", + "*.mv", + "*.mw", + "*.mx", + "*.my", + "*.mz", + "*.na", + "*.nab", + "*.nadex", + "*.nagoya", + "*.name", + "*.nationwide", + "*.natura", + "*.navy", + "*.nba", + "*.nc", + "*.ne", + "*.nec", + "*.net", + "*.netbank", + "*.netflix", + "*.network", + "*.neustar", + "*.new", + "*.newholland", + "*.news", + "*.next", + "*.nextdirect", + "*.nexus", + "*.nf", + "*.nfl", + "*.ng", + "*.ngo", + "*.nhk", + "*.ni", + "*.nico", + "*.nike", + "*.nikon", + "*.ninja", + "*.nissan", + "*.nissay", + "*.nl", + "*.no", + "*.nokia", + "*.northwesternmutual", + "*.norton", + "*.now", + "*.nowruz", + "*.nowtv", + "*.np", + "*.nr", + "*.nra", + "*.nrw", + "*.ntt", + "*.nu", + "*.nyc", + "*.nz", + "*.obi", + "*.observer", + "*.off", + "*.office", + "*.okinawa", + "*.olayan", + "*.olayangroup", + "*.oldnavy", + "*.ollo", + "*.om", + "*.omega", + "*.one", + "*.ong", + "*.onl", + "*.online", + "*.onyourside", + "*.ooo", + "*.open", + "*.oracle", + "*.orange", + "*.org", + "*.organic", + "*.origins", + "*.osaka", + "*.otsuka", + "*.ott", + "*.ovh", + "*.pa", + "*.page", + "*.panasonic", + "*.panerai", + "*.paris", + "*.pars", + "*.partners", + "*.parts", + "*.party", + "*.passagens", + "*.pay", + "*.pccw", + "*.pe", + "*.pet", + "*.pf", + "*.pfizer", + "*.pg", + "*.ph", + "*.pharmacy", + "*.phd", + "*.philips", + "*.phone", + "*.photo", + "*.photography", + "*.photos", + "*.physio", + "*.piaget", + "*.pics", + "*.pictet", + "*.pictures", + "*.pid", + "*.pin", + "*.ping", + "*.pink", + "*.pioneer", + "*.pizza", + "*.pk", + "*.pl", + "*.place", + "*.play", + "*.playstation", + "*.plumbing", + "*.plus", + "*.pm", + "*.pn", + "*.pnc", + "*.pohl", + "*.poker", + "*.politie", + "*.porn", + "*.post", + "*.pr", + "*.pramerica", + "*.praxi", + "*.press", + "*.prime", + "*.pro", + "*.prod", + "*.productions", + "*.prof", + "*.progressive", + "*.promo", + "*.properties", + "*.property", + "*.protection", + "*.pru", + "*.prudential", + "*.ps", + "*.pt", + "*.pub", + "*.pw", + "*.pwc", + "*.py", + "*.qa", + "*.qpon", + "*.quebec", + "*.quest", + "*.qvc", + "*.racing", + "*.radio", + "*.raid", + "*.re", + "*.read", + "*.realestate", + "*.realtor", + "*.realty", + "*.recipes", + "*.red", + "*.redstone", + "*.redumbrella", + "*.rehab", + "*.reise", + "*.reisen", + "*.reit", + "*.reliance", + "*.ren", + "*.rent", + "*.rentals", + "*.repair", + "*.report", + "*.republican", + "*.rest", + "*.restaurant", + "*.review", + "*.reviews", + "*.rexroth", + "*.rich", + "*.richardli", + "*.ricoh", + "*.rightathome", + "*.ril", + "*.rio", + "*.rip", + "*.rmit", + "*.ro", + "*.rocher", + "*.rocks", + "*.rodeo", + "*.rogers", + "*.room", + "*.rs", + "*.rsvp", + "*.ru", + "*.rugby", + "*.ruhr", + "*.run", + "*.rw", + "*.rwe", + "*.ryukyu", + "*.sa", + "*.saarland", + "*.safe", + "*.safety", + "*.sakura", + "*.sale", + "*.salon", + "*.samsclub", + "*.samsung", + "*.sandvik", + "*.sandvikcoromant", + "*.sanofi", + "*.sap", + "*.sarl", + "*.sas", + "*.save", + "*.saxo", + "*.sb", + "*.sbi", + "*.sbs", + "*.sc", + "*.sca", + "*.scb", + "*.schaeffler", + "*.schmidt", + "*.scholarships", + "*.school", + "*.schule", + "*.schwarz", + "*.science", + "*.scjohnson", + "*.scor", + "*.scot", + "*.sd", + "*.se", + "*.search", + "*.seat", + "*.secure", + "*.security", + "*.seek", + "*.select", + "*.sener", + "*.services", + "*.ses", + "*.seven", + "*.sew", + "*.sex", + "*.sexy", + "*.sfr", + "*.sg", + "*.sh", + "*.shangrila", + "*.sharp", + "*.shaw", + "*.shell", + "*.shia", + "*.shiksha", + "*.shoes", + "*.shop", + "*.shopping", + "*.shouji", + "*.show", + "*.showtime", + "*.shriram", + "*.si", + "*.silk", + "*.sina", + "*.singles", + "*.site", + "*.sj", + "*.sk", + "*.ski", + "*.skin", + "*.sky", + "*.skype", + "*.sl", + "*.sling", + "*.sm", + "*.smart", + "*.smile", + "*.sn", + "*.sncf", + "*.so", + "*.soccer", + "*.social", + "*.softbank", + "*.software", + "*.sohu", + "*.solar", + "*.solutions", + "*.song", + "*.sony", + "*.soy", + "*.space", + "*.spiegel", + "*.sport", + "*.spot", + "*.spreadbetting", + "*.sr", + "*.srl", + "*.srt", + "*.st", + "*.stada", + "*.staples", + "*.star", + "*.starhub", + "*.statebank", + "*.statefarm", + "*.statoil", + "*.stc", + "*.stcgroup", + "*.stockholm", + "*.storage", + "*.store", + "*.stream", + "*.studio", + "*.study", + "*.style", + "*.su", + "*.sucks", + "*.supplies", + "*.supply", + "*.support", + "*.surf", + "*.surgery", + "*.suzuki", + "*.sv", + "*.swatch", + "*.swiftcover", + "*.swiss", + "*.sx", + "*.sy", + "*.sydney", + "*.symantec", + "*.systems", + "*.sz", + "*.tab", + "*.taipei", + "*.talk", + "*.taobao", + "*.target", + "*.tatamotors", + "*.tatar", + "*.tattoo", + "*.tax", + "*.taxi", + "*.tc", + "*.tci", + "*.td", + "*.tdk", + "*.team", + "*.tech", + "*.technology", + "*.tel", + "*.telecity", + "*.telefonica", + "*.temasek", + "*.tennis", + "*.teva", + "*.tf", + "*.tg", + "*.th", + "*.thd", + "*.theater", + "*.theatre", + "*.tiaa", + "*.tickets", + "*.tienda", + "*.tiffany", + "*.tips", + "*.tires", + "*.tirol", + "*.tj", + "*.tjmaxx", + "*.tjx", + "*.tk", + "*.tkmaxx", + "*.tl", + "*.tm", + "*.tmall", + "*.tn", + "*.to", + "*.today", + "*.tokyo", + "*.tools", + "*.top", + "*.toray", + "*.toshiba", + "*.total", + "*.tours", + "*.town", + "*.toyota", + "*.toys", + "*.tr", + "*.trade", + "*.trading", + "*.training", + "*.travel", + "*.travelchannel", + "*.travelers", + "*.travelersinsurance", + "*.trust", + "*.trv", + "*.tt", + "*.tube", + "*.tui", + "*.tunes", + "*.tushu", + "*.tv", + "*.tvs", + "*.tw", + "*.tz", + "*.ua", + "*.ubank", + "*.ubs", + "*.uconnect", + "*.ug", + "*.uk", + "*.unicom", + "*.university", + "*.uno", + "*.uol", + "*.ups", + "*.us", + "*.uy", + "*.uz", + "*.va", + "*.vacations", + "*.vana", + "*.vanguard", + "*.vc", + "*.ve", + "*.vegas", + "*.ventures", + "*.verisign", + "*.versicherung", + "*.vet", + "*.vg", + "*.vi", + "*.viajes", + "*.video", + "*.vig", + "*.viking", + "*.villas", + "*.vin", + "*.vip", + "*.virgin", + "*.visa", + "*.vision", + "*.vista", + "*.vistaprint", + "*.viva", + "*.vivo", + "*.vlaanderen", + "*.vn", + "*.vodka", + "*.volkswagen", + "*.volvo", + "*.vote", + "*.voting", + "*.voto", + "*.voyage", + "*.vu", + "*.vuelos", + "*.wales", + "*.walmart", + "*.walter", + "*.wang", + "*.wanggou", + "*.warman", + "*.watch", + "*.watches", + "*.weather", + "*.weatherchannel", + "*.webcam", + "*.weber", + "*.website", + "*.wed", + "*.wedding", + "*.weibo", + "*.weir", + "*.wf", + "*.whoswho", + "*.wien", + "*.wiki", + "*.williamhill", + "*.win", + "*.windows", + "*.wine", + "*.winners", + "*.wme", + "*.wolterskluwer", + "*.woodside", + "*.work", + "*.works", + "*.world", + "*.wow", + "*.ws", + "*.wtc", + "*.wtf", + "*.xbox", + "*.xerox", + "*.xfinity", + "*.xihuan", + "*.xin", + "*.xn--11b4c3d", + "*.xn--1ck2e1b", + "*.xn--1qqw23a", + "*.xn--2scrj9c", + "*.xn--30rr7y", + "*.xn--3bst00m", + "*.xn--3ds443g", + "*.xn--3e0b707e", + "*.xn--3hcrj9c", + "*.xn--3oq18vl8pn36a", + "*.xn--3pxu8k", + "*.xn--42c2d9a", + "*.xn--45br5cyl", + "*.xn--45brj9c", + "*.xn--45q11c", + "*.xn--4gbrim", + "*.xn--54b7fta0cc", + "*.xn--55qw42g", + "*.xn--55qx5d", + "*.xn--5su34j936bgsg", + "*.xn--5tzm5g", + "*.xn--6frz82g", + "*.xn--6qq986b3xl", + "*.xn--80adxhks", + "*.xn--80ao21a", + "*.xn--80aqecdr1a", + "*.xn--80asehdb", + "*.xn--80aswg", + "*.xn--8y0a063a", + "*.xn--90a3ac", + "*.xn--90ae", + "*.xn--90ais", + "*.xn--9dbq2a", + "*.xn--9et52u", + "*.xn--9krt00a", + "*.xn--b4w605ferd", + "*.xn--bck1b9a5dre4c", + "*.xn--c1avg", + "*.xn--c2br7g", + "*.xn--cck2b3b", + "*.xn--cg4bki", + "*.xn--clchc0ea0b2g2a9gcd", + "*.xn--czr694b", + "*.xn--czrs0t", + "*.xn--czru2d", + "*.xn--d1acj3b", + "*.xn--d1alf", + "*.xn--e1a4c", + "*.xn--eckvdtc9d", + "*.xn--efvy88h", + "*.xn--estv75g", + "*.xn--fct429k", + "*.xn--fhbei", + "*.xn--fiq228c5hs", + "*.xn--fiq64b", + "*.xn--fiqs8s", + "*.xn--fiqz9s", + "*.xn--fjq720a", + "*.xn--flw351e", + "*.xn--fpcrj9c3d", + "*.xn--fzc2c9e2c", + "*.xn--fzys8d69uvgm", + "*.xn--g2xx48c", + "*.xn--gckr3f0f", + "*.xn--gecrj9c", + "*.xn--gk3at1e", + "*.xn--h2breg3eve", + "*.xn--h2brj9c", + "*.xn--h2brj9c8c", + "*.xn--hxt814e", + "*.xn--i1b6b1a6a2e", + "*.xn--imr513n", + "*.xn--io0a7i", + "*.xn--j1aef", + "*.xn--j1amh", + "*.xn--j6w193g", + "*.xn--jlq61u9w7b", + "*.xn--jvr189m", + "*.xn--kcrx77d1x4a", + "*.xn--kprw13d", + "*.xn--kpry57d", + "*.xn--kpu716f", + "*.xn--kput3i", + "*.xn--l1acc", + "*.xn--lgbbat1ad8j", + "*.xn--mgb9awbf", + "*.xn--mgba3a3ejt", + "*.xn--mgba3a4f16a", + "*.xn--mgba7c0bbn0a", + "*.xn--mgbaakc7dvf", + "*.xn--mgbaam7a8h", + "*.xn--mgbab2bd", + "*.xn--mgbai9azgqp6j", + "*.xn--mgbayh7gpa", + "*.xn--mgbb9fbpob", + "*.xn--mgbbh1a", + "*.xn--mgbbh1a71e", + "*.xn--mgbc0a9azcg", + "*.xn--mgbca7dzdo", + "*.xn--mgberp4a5d4ar", + "*.xn--mgbgu82a", + "*.xn--mgbi4ecexp", + "*.xn--mgbpl2fh", + "*.xn--mgbt3dhd", + "*.xn--mgbtx2b", + "*.xn--mgbx4cd0ab", + "*.xn--mix891f", + "*.xn--mk1bu44c", + "*.xn--mxtq1m", + "*.xn--ngbc5azd", + "*.xn--ngbe9e0a", + "*.xn--ngbrx", + "*.xn--node", + "*.xn--nqv7f", + "*.xn--nqv7fs00ema", + "*.xn--nyqy26a", + "*.xn--o3cw4h", + "*.xn--ogbpf8fl", + "*.xn--otu796d", + "*.xn--p1acf", + "*.xn--p1ai", + "*.xn--pbt977c", + "*.xn--pgbs0dh", + "*.xn--pssy2u", + "*.xn--q9jyb4c", + "*.xn--qcka1pmc", + "*.xn--qxam", + "*.xn--rhqv96g", + "*.xn--rovu88b", + "*.xn--rvc1e0am3e", + "*.xn--s9brj9c", + "*.xn--ses554g", + "*.xn--t60b56a", + "*.xn--tckwe", + "*.xn--tiq49xqyj", + "*.xn--unup4y", + "*.xn--vermgensberater-ctb", + "*.xn--vermgensberatung-pwb", + "*.xn--vhquv", + "*.xn--vuq861b", + "*.xn--w4r85el8fhu5dnra", + "*.xn--w4rs40l", + "*.xn--wgbh1c", + "*.xn--wgbl6a", + "*.xn--xhq521b", + "*.xn--xkc2al3hye2a", + "*.xn--xkc2dl3a5ee0h", + "*.xn--y9a3aq", + "*.xn--yfro4i67o", + "*.xn--ygbi2ammx", + "*.xn--zfr164b", + "*.xperia", + "*.xxx", + "*.xyz", + "*.yachts", + "*.yahoo", + "*.yamaxun", + "*.yandex", + "*.ye", + "*.yodobashi", + "*.yoga", + "*.yokohama", + "*.you", + "*.youtube", + "*.yt", + "*.yun", + "*.za", + "*.zappos", + "*.zara", + "*.zero", + "*.zip", + "*.zippo", + "*.zm", + "*.zone", + "*.zuerich", + "*.zw", +] diff --git a/four/VPNManager.swift b/four/VPNManager.swift new file mode 100644 index 0000000..71619b1 --- /dev/null +++ b/four/VPNManager.swift @@ -0,0 +1,103 @@ +// +// VPNManager.swift +// four +// +// Created by Ryan McGrath on 12/24/18. +// Copyright © 2018 Ryan McGrath. All rights reserved. +// + +import Foundation +import NetworkExtension + +class VPNManager { + fileprivate var manager = NETunnelProviderManager() + fileprivate static let vpnDescription = "CloudFlare 1.1.1.1 DNS" + fileprivate static let vpnServerDescription = "CloudFlare 1.1.1.1 DNS" + fileprivate static let vpnDNS = ["1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001"] + + public var connected: Bool { + get { + return manager.isOnDemandEnabled + } + + set { + if newValue == connected { return } + + update(body: { [weak self] in + self?.manager.isEnabled = newValue + self?.manager.isOnDemandEnabled = newValue + }, complete: { [weak self] in + let session = self?.manager.connection as? NETunnelProviderSession + + if newValue { + do { + try session?.startVPNTunnel(options: nil) + print("STARTED!") + } catch let err as NSError { + print("\(err.localizedDescription)") + } + } else { + session?.stopVPNTunnel() + } + }) + } + } + + init() { + refreshManager() + } + + func refreshManager() -> Void { + NETunnelProviderManager.loadAllFromPreferences(completionHandler: { (managers, error) in + if nil == error { + if let managers = managers { + for manager in managers { + if manager.localizedDescription == VPNManager.vpnDescription { + self.manager = manager + return + } + } + } + } + + self.configureDNS() + }) + } + + private func update(body: @escaping () -> Void, complete: @escaping () -> Void) { + manager.loadFromPreferences { error in + if error != nil { + print("Load error: \(String(describing: error?.localizedDescription))") + return + } + + body() + + self.manager.saveToPreferences { (error) in + if nil != error { + print("vpn_connect: save error \(error!)") + } else { + complete() + } + } + } + } + + private func configureDNS() { + manager.localizedDescription = VPNManager.vpnDescription + + let proto = NETunnelProviderProtocol() + proto.providerBundleIdentifier = "com.rymc.vpntunnel.provider" + proto.serverAddress = VPNManager.vpnServerDescription + manager.protocolConfiguration = proto + + let evaluationRule = NEEvaluateConnectionRule(matchDomains: TLDS, andAction: .connectIfNeeded) + evaluationRule.useDNSServers = VPNManager.vpnDNS + + let onDemandRule = NEOnDemandRuleEvaluateConnection() + onDemandRule.connectionRules = [evaluationRule] + onDemandRule.interfaceTypeMatch = .any + + manager.onDemandRules = [onDemandRule] + } +} diff --git a/four/ViewController.swift b/four/ViewController.swift new file mode 100644 index 0000000..969e8cf --- /dev/null +++ b/four/ViewController.swift @@ -0,0 +1,92 @@ +// +// ViewController.swift +// four +// +// Created by Ryan McGrath on 12/23/18. +// Copyright © 2018 Ryan McGrath. All rights reserved. +// + +import Cocoa + +class View: NSView { + override var isFlipped: Bool { + get { return true } + } +} + +class ViewController: NSViewController { + let vpnManager = VPNManager() + + lazy var fourLabel: NSTextField = { + let label = NSTextField(labelWithString: "1.1.1.1") + label.translatesAutoresizingMaskIntoConstraints = false + label.alignment = .center + label.font = NSFont.boldSystemFont(ofSize: 30.0) + return label + }() + + lazy var toggle: JSSwitch = { + let sw = JSSwitch(frame: CGRect(x: 118.0, y: 95.0, width: 64.0, height: 44.0)) + sw.target = self + sw.action = #selector(toggleConnection) + return sw + }() + + lazy var connectionStatusLabel: NSTextField = { + let label = NSTextField(labelWithString: "DISCONNECTED") + label.translatesAutoresizingMaskIntoConstraints = false + label.alignment = .center + label.font = NSFont.boldSystemFont(ofSize: 18.0) + return label + }() + + lazy var makeThemFeelBadForNotBeingConnectedLabel: NSTextField = { + let label = NSTextField(labelWithString: "Your DNS queries are not private") + label.translatesAutoresizingMaskIntoConstraints = false + label.alignment = .center + label.font = NSFont.systemFont(ofSize: 18.0) + return label + }() + + override func loadView() { + let frame = NSRect(x: 0, y: 0, width: 300, height: 300) + let rootView = View(frame: frame) + view = rootView + } + + override func viewDidLoad() { + super.viewDidLoad() + + view.addSubview(fourLabel) + view.addSubview(toggle) + view.addSubview(connectionStatusLabel) + view.addSubview(makeThemFeelBadForNotBeingConnectedLabel) + + NSLayoutConstraint.activate([ + fourLabel.topAnchor.constraint(equalTo: view.topAnchor, constant: 30.0), + fourLabel.widthAnchor.constraint(equalToConstant: 300.0), + fourLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor), + + connectionStatusLabel.topAnchor.constraint(equalTo: fourLabel.bottomAnchor, constant: 120.0), + connectionStatusLabel.widthAnchor.constraint(equalToConstant: 300.0), + connectionStatusLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor), + + makeThemFeelBadForNotBeingConnectedLabel.topAnchor.constraint(equalTo: connectionStatusLabel.bottomAnchor, constant: 20.0), + makeThemFeelBadForNotBeingConnectedLabel.widthAnchor.constraint(equalToConstant: 300.0), + makeThemFeelBadForNotBeingConnectedLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor) + ]) + } + + @objc + func toggleConnection() { + vpnManager.connected = !vpnManager.connected + + if vpnManager.connected { + connectionStatusLabel.stringValue = "CONNECTED" + makeThemFeelBadForNotBeingConnectedLabel.stringValue = "Your DNS queries are now\n private and faster" + } else { + connectionStatusLabel.stringValue = "DISCONNECTED" + makeThemFeelBadForNotBeingConnectedLabel.stringValue = "Your DNS queries are not private" + } + } +} diff --git a/four/four.entitlements b/four/four.entitlements new file mode 100644 index 0000000..d25f3d4 --- /dev/null +++ b/four/four.entitlements @@ -0,0 +1,16 @@ + + + + + com.apple.developer.networking.networkextension + + packet-tunnel-provider + + com.apple.developer.networking.vpn.api + + allow-vpn + + com.apple.security.app-sandbox + + + diff --git a/four/main.swift b/four/main.swift new file mode 100644 index 0000000..08e4bef --- /dev/null +++ b/four/main.swift @@ -0,0 +1,20 @@ +// +// main.swift +// four +// +// Created by Ryan McGrath on 12/23/18. +// Copyright © 2018 Ryan McGrath. All rights reserved. +// + +import Cocoa +import Foundation + +autoreleasepool { + let delegate = AppDelegate() + withExtendedLifetime(delegate, { + let application = NSApplication.shared + application.delegate = delegate + application.run() + application.delegate = nil + }) +} diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..a4278d1 --- /dev/null +++ b/readme.md @@ -0,0 +1,23 @@ +# 1.1.1.1 on macOS +[CloudFlare has a cool app for iOS devices](https://itunes.apple.com/us/app/1-1-1-1-faster-internet/id1423538627) that enables easy switching to faster, private, and encrypted DNS queries. While switching DNS isn't exactly difficult, I found the user experience to be interesting - very easy to configure, just tap a switch and you're in. + +Another interesting thing about it is that, unless they're doing some super custom stuff (which is totally possible), I'm gonna hazard a guess that it has to be built on top of `NetworkExtension` APIs. With the exception of smaller things like [NEDNSProxyProvider](https://developer.apple.com/documentation/networkextension/nednsproxyprovider), most of that stuff should work fine on macOS now... but very few companies ever take the time to take an almost-compatible iOS codebase and shim it for Cocoa/AppKit. The UI used on `1.1.1.1` isn't too difficult to do in macOS, though. + +Thus, over the holidays I dug in to see how annoying it'd be to do. This implements a very basic VPN tunnel that sets DNS to go over 1.1.1.1, in a basic UI as a status bar menu app. Some screenshots are below. I probably won't be pursuing this further in lieu of working on other projects, so anyone out there should feel free to take this and extend it as they wish. + +Also, side note: you probably want a true VPN instead of this, but this isn't a bad approach either in the grand scheme of things. Probably one where doing your research is worthwhile. :) + +## Screenshots +![Disconnected](https://github.com/ryanmcgrath/four/blob/master/screenshots/disconnected.png?raw=true) +![Connected](https://github.com/ryanmcgrath/flour/blob/master/screenshots/connected.png?raw=true) + +## What else is here? +This repo could also be used as scaffolding/reference for a nibless Swift Cocoa app, if you're into that sorta thing. I personally think Interface Builder makes anyone who deals with UI in code (web devs, etc) groan out loud, so maybe this goes towards showing it's not _that_ difficult or outlandish to do otherwise. + +- It implements a taskbar app with a custom `NSPopover` view in code. +- It implements a working `UISwitch` replacement, using a slightly-modified [JSSwitch](https://github.com/juliensagot/JSSwitch). + +## License, etc +This is very much a "do-wtf-you-want-with-it" license. Code is as-is. I'd like to give props to [this list of TLDs by popmedic](https://gist.github.com/popmedic/cf9472aa8c2adda875a484c5a1c5da06), because compiling it myself would've been annoying. + +Questions can be directed to [ryan@rymc.io](mailto:ryan@rymc.io) or [@ryanmcgrath on Twitter](https://twitter.com/ryanmcgrath). diff --git a/screenshots/connected.png b/screenshots/connected.png new file mode 100644 index 0000000..f62a251 Binary files /dev/null and b/screenshots/connected.png differ diff --git a/screenshots/disconnected.png b/screenshots/disconnected.png new file mode 100644 index 0000000..f221415 Binary files /dev/null and b/screenshots/disconnected.png differ