Initial, holiday fun

This commit is contained in:
Ryan McGrath 2018-12-26 18:34:03 -08:00
commit abcab0c509
No known key found for this signature in database
GPG key ID: 811674B62B666830
18 changed files with 2638 additions and 0 deletions

25
.gitignore vendored Normal file
View file

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

View file

@ -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 = "<group>"; };
2E7CEEE821CF8E1400BB164D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
2E7CEEED21CF8E1400BB164D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
2E7CEEEE21CF8E1400BB164D /* four.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = four.entitlements; sourceTree = "<group>"; };
2E7CEEF421CF904700BB164D /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
2E7CEEF621D0BB6500BB164D /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
2E7CEEF821D0BEE500BB164D /* Icons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Icons.swift; sourceTree = "<group>"; };
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 = "<group>"; };
2E7CEF0121D0D65500BB164D /* VPNManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNManager.swift; sourceTree = "<group>"; };
2E7CEF0321D0E72000BB164D /* TLDList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TLDList.swift; sourceTree = "<group>"; };
/* 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 = "<group>";
};
2E7CEEE421CF8E1300BB164D /* Products */ = {
isa = PBXGroup;
children = (
2E7CEEE321CF8E1300BB164D /* four.app */,
);
name = Products;
sourceTree = "<group>";
};
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 = "<group>";
};
2E7CEEFA21D0C33200BB164D /* Frameworks */ = {
isa = PBXGroup;
children = (
2E7CEEFD21D0C34C00BB164D /* NetworkExtension.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
/* 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 */;
}

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:four.xcodeproj">
</FileRef>
</Workspace>

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

44
four/AppDelegate.swift Normal file
View file

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

View file

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

View file

@ -0,0 +1,6 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}

112
four/Icons.swift Normal file
View file

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

32
four/Info.plist Normal file
View file

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIconFile</key>
<string></string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2018 Ryan McGrath. All rights reserved.</string>
<key>LSUIElement</key>
<true/>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
</dict>
</plist>

181
four/JSSwitch.swift Normal file
View file

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

1551
four/TLDList.swift Normal file

File diff suppressed because it is too large Load diff

103
four/VPNManager.swift Normal file
View file

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

92
four/ViewController.swift Normal file
View file

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

16
four/four.entitlements Normal file
View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.networking.networkextension</key>
<array>
<string>packet-tunnel-provider</string>
</array>
<key>com.apple.developer.networking.vpn.api</key>
<array>
<string>allow-vpn</string>
</array>
<key>com.apple.security.app-sandbox</key>
<true/>
</dict>
</plist>

20
four/main.swift Normal file
View file

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

23
readme.md Normal file
View file

@ -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).

BIN
screenshots/connected.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB