├── com.kishikawakatsumi.BandwidthLimiter.helper ├── main.swift ├── com.kishikawakatsumi.BandwidthLimiter.helper-Bridging-Header.h ├── OSStatus+Extensions.swift ├── AuditTokenHack.m ├── AuditTokenHack.h ├── launchd.plist ├── Info.plist ├── HelperExecutionService.swift ├── Helper.swift └── ConnectionIdentityService.swift ├── BandwidthLimiter ├── Assets.xcassets │ ├── Contents.json │ ├── AppIcon.appiconset │ │ ├── 128.png │ │ ├── 16.png │ │ ├── 256.png │ │ ├── 32.png │ │ ├── 512.png │ │ ├── 64.png │ │ ├── 1024.png │ │ └── Contents.json │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── tachometer-slow-solid.imageset │ │ ├── tachometer-slow-solid.png │ │ ├── tachometer-slow-solid@2x.png │ │ ├── tachometer-slow-solid@3x.png │ │ └── Contents.json │ └── tachometer-fast-regular.imageset │ │ ├── tachometer-fast-regular.png │ │ ├── tachometer-fast-regular@2x.png │ │ ├── tachometer-fast-regular@3x.png │ │ └── Contents.json ├── AppState.swift ├── Setting.swift ├── BandwidthLimiter.entitlements ├── ExecutionService.swift ├── TextFieldCell.swift ├── Info.plist ├── App.swift ├── NewProfileNameViewController.swift ├── Profile.swift ├── TrafficShaper.swift ├── TemporaryFile.swift ├── AppDelegate.swift ├── ExecutionServiceProxy.swift ├── SettingsViewController.swift ├── ManageProfilesViewController.swift └── Base.lproj │ └── Main.storyboard ├── Shared ├── RemoteApplicationProtocol.swift ├── HelperProtocol.swift ├── HelperConstants.swift ├── Result+Extensions.swift └── Errors.swift ├── BandwidthLimiter.xcodeproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── xcuserdata │ └── katsumi.kishikawa.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist └── project.pbxproj ├── uninstall_privileged_helper.sh ├── LICENSE ├── README.md └── .gitignore /com.kishikawakatsumi.BandwidthLimiter.helper/main.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | let helper = Helper() 4 | helper.run() 5 | -------------------------------------------------------------------------------- /BandwidthLimiter/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /com.kishikawakatsumi.BandwidthLimiter.helper/com.kishikawakatsumi.BandwidthLimiter.helper-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "AuditTokenHack.h" 2 | -------------------------------------------------------------------------------- /Shared/RemoteApplicationProtocol.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | @objc(MainApplicationProtocol) 4 | public protocol RemoteApplicationProtocol {} 5 | -------------------------------------------------------------------------------- /BandwidthLimiter/Assets.xcassets/AppIcon.appiconset/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kishikawakatsumi/BandwidthLimiter/HEAD/BandwidthLimiter/Assets.xcassets/AppIcon.appiconset/128.png -------------------------------------------------------------------------------- /BandwidthLimiter/Assets.xcassets/AppIcon.appiconset/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kishikawakatsumi/BandwidthLimiter/HEAD/BandwidthLimiter/Assets.xcassets/AppIcon.appiconset/16.png -------------------------------------------------------------------------------- /BandwidthLimiter/Assets.xcassets/AppIcon.appiconset/256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kishikawakatsumi/BandwidthLimiter/HEAD/BandwidthLimiter/Assets.xcassets/AppIcon.appiconset/256.png -------------------------------------------------------------------------------- /BandwidthLimiter/Assets.xcassets/AppIcon.appiconset/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kishikawakatsumi/BandwidthLimiter/HEAD/BandwidthLimiter/Assets.xcassets/AppIcon.appiconset/32.png -------------------------------------------------------------------------------- /BandwidthLimiter/Assets.xcassets/AppIcon.appiconset/512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kishikawakatsumi/BandwidthLimiter/HEAD/BandwidthLimiter/Assets.xcassets/AppIcon.appiconset/512.png -------------------------------------------------------------------------------- /BandwidthLimiter/Assets.xcassets/AppIcon.appiconset/64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kishikawakatsumi/BandwidthLimiter/HEAD/BandwidthLimiter/Assets.xcassets/AppIcon.appiconset/64.png -------------------------------------------------------------------------------- /BandwidthLimiter/Assets.xcassets/AppIcon.appiconset/1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kishikawakatsumi/BandwidthLimiter/HEAD/BandwidthLimiter/Assets.xcassets/AppIcon.appiconset/1024.png -------------------------------------------------------------------------------- /BandwidthLimiter/AppState.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct AppState: Hashable, Codable { 4 | var isActive: Bool 5 | var settings: [Setting] 6 | 7 | var customProfiles = [Profile]() 8 | } 9 | -------------------------------------------------------------------------------- /BandwidthLimiter/Setting.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct Setting: Hashable, Codable { 4 | var host: String? 5 | var port: String? 6 | var profile: Profile 7 | var isActive: Bool 8 | } 9 | -------------------------------------------------------------------------------- /BandwidthLimiter.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /BandwidthLimiter/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /BandwidthLimiter/Assets.xcassets/tachometer-slow-solid.imageset/tachometer-slow-solid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kishikawakatsumi/BandwidthLimiter/HEAD/BandwidthLimiter/Assets.xcassets/tachometer-slow-solid.imageset/tachometer-slow-solid.png -------------------------------------------------------------------------------- /BandwidthLimiter/BandwidthLimiter.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /BandwidthLimiter/Assets.xcassets/tachometer-slow-solid.imageset/tachometer-slow-solid@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kishikawakatsumi/BandwidthLimiter/HEAD/BandwidthLimiter/Assets.xcassets/tachometer-slow-solid.imageset/tachometer-slow-solid@2x.png -------------------------------------------------------------------------------- /BandwidthLimiter/Assets.xcassets/tachometer-slow-solid.imageset/tachometer-slow-solid@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kishikawakatsumi/BandwidthLimiter/HEAD/BandwidthLimiter/Assets.xcassets/tachometer-slow-solid.imageset/tachometer-slow-solid@3x.png -------------------------------------------------------------------------------- /BandwidthLimiter/Assets.xcassets/tachometer-fast-regular.imageset/tachometer-fast-regular.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kishikawakatsumi/BandwidthLimiter/HEAD/BandwidthLimiter/Assets.xcassets/tachometer-fast-regular.imageset/tachometer-fast-regular.png -------------------------------------------------------------------------------- /BandwidthLimiter/Assets.xcassets/tachometer-fast-regular.imageset/tachometer-fast-regular@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kishikawakatsumi/BandwidthLimiter/HEAD/BandwidthLimiter/Assets.xcassets/tachometer-fast-regular.imageset/tachometer-fast-regular@2x.png -------------------------------------------------------------------------------- /BandwidthLimiter/Assets.xcassets/tachometer-fast-regular.imageset/tachometer-fast-regular@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kishikawakatsumi/BandwidthLimiter/HEAD/BandwidthLimiter/Assets.xcassets/tachometer-fast-regular.imageset/tachometer-fast-regular@3x.png -------------------------------------------------------------------------------- /Shared/HelperProtocol.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | @objc(HelperProtocol) 4 | public protocol HelperProtocol { 5 | func getVersion(completion: @escaping (String) -> Void) 6 | func executeScript(at path: String, options: [String], completion: @escaping (String?, Error?) -> Void) 7 | } 8 | -------------------------------------------------------------------------------- /BandwidthLimiter.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /com.kishikawakatsumi.BandwidthLimiter.helper/OSStatus+Extensions.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension OSStatus { 4 | var hasSecError: Bool { self != errSecSuccess } 5 | 6 | var secErrorDescription: String { 7 | let error = SecCopyErrorMessageString(self, nil) as String? ?? "Unknown error" 8 | return error 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Shared/HelperConstants.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | enum HelperConstants { 4 | static let bundleID = "com.kishikawakatsumi.BandwidthLimiter" 5 | static let domain = "\(bundleID).helper" 6 | static let helpersFolder = "/Library/PrivilegedHelperTools/" 7 | static let helperPath = helpersFolder + domain 8 | static let subject = "27AEDK3C9F" 9 | } 10 | -------------------------------------------------------------------------------- /com.kishikawakatsumi.BandwidthLimiter.helper/AuditTokenHack.m: -------------------------------------------------------------------------------- 1 | #import "AuditTokenHack.h" 2 | 3 | @implementation AuditTokenHack 4 | 5 | + (NSData *)getAuditTokenDataFromNSXPCConnection:(NSXPCConnection *)connection { 6 | audit_token_t auditToken = connection.auditToken; 7 | return [NSData dataWithBytes:&auditToken length:sizeof(audit_token_t)]; 8 | } 9 | 10 | @end 11 | -------------------------------------------------------------------------------- /com.kishikawakatsumi.BandwidthLimiter.helper/AuditTokenHack.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | // Hack to get the private auditToken property 4 | @interface NSXPCConnection(PrivateAuditToken) 5 | 6 | @property (nonatomic, readonly) audit_token_t auditToken; 7 | 8 | @end 9 | 10 | // Interface for AuditTokenHack 11 | @interface AuditTokenHack : NSObject 12 | 13 | +(NSData *)getAuditTokenDataFromNSXPCConnection:(NSXPCConnection *)connection; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /com.kishikawakatsumi.BandwidthLimiter.helper/launchd.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Label 6 | com.kishikawakatsumi.BandwidthLimiter.helper 7 | MachServices 8 | 9 | com.kishikawakatsumi.BandwidthLimiter.helper 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /BandwidthLimiter/ExecutionService.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | final class ExecutionService { 4 | static let shared = ExecutionService() 5 | static var isHelperInstalled: Bool { FileManager().fileExists(atPath: HelperConstants.helperPath) } 6 | 7 | func executeScript(at path: String, options: [String], completion: @escaping (Result) -> Void) throws { 8 | let proxy = try ExecutionServiceProxy().getProxy() 9 | proxy.executeScript(at: path, options: options) { (output, error) in 10 | completion(Result(string: output, error: error)) 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /uninstall_privileged_helper.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | PRIVILEGED_HELPER_LABEL=com.kishikawakatsumi.BandwidthLimiter.helper 4 | 5 | sudo rm /Library/PrivilegedHelperTools/$PRIVILEGED_HELPER_LABEL 6 | sudo rm /Library/LaunchDaemons/$PRIVILEGED_HELPER_LABEL.plist 7 | sudo launchctl bootout system/$PRIVILEGED_HELPER_LABEL #'Boot-out failed: 36: Operation now in progress' is OK output 8 | 9 | echo "Querying launchd..." 10 | LAUNCHD_OUTPUT=$(sudo launchctl list | grep $PRIVILEGED_HELPER_LABEL) 11 | 12 | if [ -z "$LAUNCHD_OUTPUT" ] 13 | then 14 | echo "Finished successfully." 15 | else 16 | echo "WARNING: $PRIVILEGED_HELPER_LABEL is not removed" 17 | fi 18 | -------------------------------------------------------------------------------- /BandwidthLimiter.xcodeproj/xcuserdata/katsumi.kishikawa.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | BandwidthLimiter.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | com.kishikawakatsumi.BandwidthLimiter.helper.xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 1 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /BandwidthLimiter/Assets.xcassets/tachometer-slow-solid.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "tachometer-slow-solid.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "tachometer-slow-solid@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "tachometer-slow-solid@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | }, 23 | "properties" : { 24 | "template-rendering-intent" : "template" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /BandwidthLimiter/Assets.xcassets/tachometer-fast-regular.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "tachometer-fast-regular.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "tachometer-fast-regular@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "tachometer-fast-regular@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | }, 23 | "properties" : { 24 | "template-rendering-intent" : "template" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Shared/Result+Extensions.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension Result where Success == String, Failure == Error { 4 | 5 | var string: Success? { 6 | if case let Self.success(string) = self { 7 | return string 8 | } 9 | return nil 10 | } 11 | 12 | var error: Failure? { 13 | if case let Self.failure(error) = self { 14 | return error 15 | } 16 | return nil 17 | } 18 | 19 | init(string: String?, error: Error?) { 20 | if let string = string { 21 | self = .success(string) 22 | } else if let error = error { 23 | self = .failure(error) 24 | } else { 25 | self = .failure(ExecutionError.unknown) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /com.kishikawakatsumi.BandwidthLimiter.helper/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleIdentifier 6 | com.kishikawakatsumi.BandwidthLimiter.helper 7 | CFBundleInfoDictionaryVersion 8 | 6.0 9 | CFBundleShortVersionString 10 | 0.1.3 11 | CFBundleVersion 12 | 4 13 | SMAuthorizedClients 14 | 15 | identifier "com.kishikawakatsumi.BandwidthLimiter" and anchor apple generic and certificate leaf[subject.CN] = "Developer ID Application: Katsumi Kishikawa (27AEDK3C9F)" 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /Shared/Errors.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | enum AuthorizationError: LocalizedError { 4 | case helperInstallation(String) 5 | case helperConnection(String) 6 | case unknown 7 | 8 | var errorDescription: String? { 9 | switch self { 10 | case .helperInstallation(let description): return "Helper installation error. \(description)" 11 | case .helperConnection(let description): return "Helper connection error. \(description)" 12 | case .unknown: return "Unknown error" 13 | } 14 | } 15 | } 16 | 17 | enum ExecutionError: LocalizedError { 18 | case invalidStringConversion 19 | case unknown 20 | 21 | var errorDescription: String? { 22 | switch self { 23 | case .invalidStringConversion: return "The output data is not convertible to a String (utf8)" 24 | case .unknown: return "Unknown error" 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /BandwidthLimiter/TextFieldCell.swift: -------------------------------------------------------------------------------- 1 | import AppKit 2 | 3 | final class TextFieldCell: NSTextFieldCell { 4 | func adjustedRect(forBounds rect: NSRect) -> NSRect { 5 | let offset: CGFloat = floor((rect.height - ((font?.ascender ?? 0.0) - (font?.descender ?? 0.0))) / 2) 6 | return rect.insetBy(dx: 0, dy: offset) 7 | } 8 | 9 | override func edit(withFrame rect: NSRect, in controlView: NSView, editor textObj: NSText, delegate: Any?, event: NSEvent?) { 10 | super.edit(withFrame: adjustedRect(forBounds: rect), in: controlView, editor: textObj, delegate: textObj, event: event) 11 | } 12 | 13 | override func select(withFrame rect: NSRect, in controlView: NSView, editor textObj: NSText, delegate: Any?, start selStart: Int, length selLength: Int) { 14 | super.select(withFrame: adjustedRect(forBounds: rect), in: controlView, editor: textObj, delegate: delegate, start: selStart, length: selLength) 15 | } 16 | 17 | override func drawInterior(withFrame cellFrame: NSRect, in controlView: NSView) { 18 | super.drawInterior(withFrame: adjustedRect(forBounds: cellFrame), in: controlView) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Kishikawa Katsumi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /com.kishikawakatsumi.BandwidthLimiter.helper/HelperExecutionService.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct HelperExecutionService { 4 | static let shared = HelperExecutionService() 5 | static private let executableURL = URL(fileURLWithPath: "/bin/sh") 6 | 7 | private let queue = DispatchQueue(label: "HelperExecutionService") 8 | 9 | func executeScript(at path: String, options: [String], completion: @escaping (Result) -> Void) throws { 10 | let process = Process() 11 | process.executableURL = Self.executableURL 12 | process.arguments = [path] + options 13 | 14 | let outputPipe = Pipe() 15 | process.standardOutput = outputPipe 16 | process.standardError = outputPipe 17 | try process.run() 18 | 19 | queue.async { 20 | process.waitUntilExit() 21 | 22 | let outputData = outputPipe.fileHandleForReading.readDataToEndOfFile() 23 | 24 | guard let output = String(data: outputData, encoding: .utf8) else { 25 | completion(.failure(ExecutionError.invalidStringConversion)) 26 | return 27 | } 28 | 29 | completion(.success(output)) 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bandwidth Limiter for Mac 2 | 3 | The Mac application can quickly put a bandwidth limit on the network from the menu bar. 4 | 5 | Screen Shot 2021-05-06 at 21 26 36 6 | 7 | In addition, you can set a different speed limit for each host. 8 | 9 | Screen Shot 2021-05-06 at 21 23 34 10 | 11 | 12 | If you leave the hostname blank, all network access will be restricted. 13 | 14 | Screen Shot 2021-05-06 at 21 31 22 15 | 16 | 17 | **Status Icon** 18 | 19 | No bandwidth limitation 20 | 21 | Screen Shot 2021-05-06 at 21 25 07 22 | 23 | Bandwidth limiting enabled 24 | 25 | Screen Shot 2021-05-06 at 21 32 40 26 | 27 | 28 | ## Download 29 | 30 | https://github.com/kishikawakatsumi/BandwidthLimiter/releases/latest 31 | -------------------------------------------------------------------------------- /BandwidthLimiter/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | {"images":[{"size":"128x128","expected-size":"128","filename":"128.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"256x256","expected-size":"256","filename":"256.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"128x128","expected-size":"256","filename":"256.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"},{"size":"256x256","expected-size":"512","filename":"512.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"},{"size":"32x32","expected-size":"32","filename":"32.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"512x512","expected-size":"512","filename":"512.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"16x16","expected-size":"16","filename":"16.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"16x16","expected-size":"32","filename":"32.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"},{"size":"32x32","expected-size":"64","filename":"64.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"},{"size":"512x512","expected-size":"1024","filename":"1024.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"}]} -------------------------------------------------------------------------------- /BandwidthLimiter/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 19 | CFBundleShortVersionString 20 | 0.1.3 21 | CFBundleVersion 22 | 4 23 | LSApplicationCategoryType 24 | public.app-category.developer-tools 25 | LSMinimumSystemVersion 26 | $(MACOSX_DEPLOYMENT_TARGET) 27 | LSUIElement 28 | 29 | NSMainStoryboardFile 30 | Main 31 | NSPrincipalClass 32 | NSApplication 33 | SMPrivilegedExecutables 34 | 35 | com.kishikawakatsumi.BandwidthLimiter.helper 36 | identifier "com.kishikawakatsumi.BandwidthLimiter.helper" and anchor apple generic and certificate leaf[subject.CN] = "Developer ID Application: Katsumi Kishikawa (27AEDK3C9F)" 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /BandwidthLimiter/App.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Combine 3 | 4 | final class App { 5 | static let shared = App() 6 | 7 | @Published 8 | var appState: AppState { 9 | didSet { 10 | try? save() 11 | } 12 | } 13 | 14 | var documentUrl: URL? { 15 | let fileManager = FileManager() 16 | guard let appSupportUrl = fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask).first else { 17 | return nil 18 | } 19 | 20 | let directoryUrl = appSupportUrl.appendingPathComponent("com.kishikawakatsumi.BandwidthLimiter") 21 | do { 22 | try fileManager.createDirectory (at: directoryUrl, withIntermediateDirectories: true, attributes: nil) 23 | } catch { 24 | return nil 25 | } 26 | 27 | return directoryUrl.appendingPathComponent("app_state.json") 28 | } 29 | 30 | init() { 31 | appState = AppState(isActive: false, settings: [Setting(host: nil, port: nil, profile: .default, isActive: false)]) 32 | guard let documentUrl = documentUrl else { return } 33 | 34 | let decoder = JSONDecoder() 35 | if let appState = try? decoder.decode(AppState.self, from: Data(contentsOf: documentUrl)) { 36 | self.appState = appState 37 | } 38 | } 39 | 40 | private func save() throws { 41 | guard let documentUrl = documentUrl else { return } 42 | 43 | let encoder = JSONEncoder() 44 | let data = try encoder.encode(appState) 45 | try data.write(to: documentUrl, options: .atomic) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /BandwidthLimiter/NewProfileNameViewController.swift: -------------------------------------------------------------------------------- 1 | import AppKit 2 | import Combine 3 | 4 | class NewProfileNameViewController: NSViewController { 5 | @IBOutlet var newProfileNameField: NSTextField! 6 | @IBOutlet var createButton: NSButton! 7 | 8 | let profileAdded = PassthroughSubject() 9 | var temporaryAppState: AppState? 10 | 11 | override func viewDidLoad() { 12 | super.viewDidLoad() 13 | createButton.keyEquivalent = "\r" 14 | } 15 | 16 | @IBAction 17 | func save(_ sender: NSButton) { 18 | guard var temporaryAppState = temporaryAppState else { return } 19 | var customProfiles = temporaryAppState.customProfiles 20 | 21 | let name = newProfileNameField.stringValue 22 | if (Profile.presets + customProfiles).contains(where: { $0.title == name }) { 23 | let alert = NSAlert() 24 | alert.messageText = "Profile with same name exists" 25 | alert.runModal() 26 | return 27 | } 28 | 29 | customProfiles.append( 30 | Profile( 31 | title: name, 32 | downBandwidth: "0Kbit/s", downPacketLossRate: "0.0", downDelay: "0", 33 | upBandwidth: "0Kbit/s", upPacketLossRate: "0.0", upDelay: "0" 34 | ) 35 | ) 36 | temporaryAppState.customProfiles = customProfiles 37 | profileAdded.send(temporaryAppState) 38 | 39 | dismiss(self) 40 | } 41 | 42 | @IBAction 43 | func cancel(_ sender: NSButton) { 44 | dismiss(self) 45 | } 46 | } 47 | 48 | extension NewProfileNameViewController: NSTextFieldDelegate { 49 | func controlTextDidChange(_ notification: Notification) { 50 | createButton.isEnabled = !newProfileNameField.stringValue.isEmpty 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /com.kishikawakatsumi.BandwidthLimiter.helper/Helper.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | final class Helper: NSObject, NSXPCListenerDelegate, HelperProtocol { 4 | private let listener: NSXPCListener 5 | private let service = HelperExecutionService.shared 6 | 7 | override init() { 8 | self.listener = NSXPCListener(machServiceName: HelperConstants.domain) 9 | super.init() 10 | self.listener.delegate = self 11 | } 12 | 13 | func getVersion(completion: (String) -> Void) { 14 | completion(Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "0") 15 | } 16 | 17 | func executeScript(at path: String, options: [String], completion: @escaping (String?, Error?) -> Void) { 18 | NSLog("Executing script at \(path)") 19 | do { 20 | try service.executeScript(at: path, options: options) { (result) in 21 | NSLog("Output: \(result.string ?? ""). Error: \(result.error?.localizedDescription ?? "")") 22 | completion(result.string, result.error) 23 | } 24 | } catch { 25 | NSLog("Error: \(error.localizedDescription)") 26 | completion(nil, error) 27 | } 28 | } 29 | 30 | func run() { 31 | self.listener.resume() 32 | RunLoop.current.run() 33 | } 34 | 35 | func listener(_ listener: NSXPCListener, shouldAcceptNewConnection newConnection: NSXPCConnection) -> Bool { 36 | guard ConnectionIdentityService.isConnectionValid(connection: newConnection) else { return false } 37 | 38 | newConnection.exportedInterface = NSXPCInterface(with: HelperProtocol.self) 39 | newConnection.remoteObjectInterface = NSXPCInterface(with: RemoteApplicationProtocol.self) 40 | newConnection.exportedObject = self 41 | 42 | newConnection.resume() 43 | 44 | return true 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /BandwidthLimiter/Profile.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct Profile: Hashable, Codable { 4 | var title: String 5 | var downBandwidth: String 6 | var downPacketLossRate: String 7 | var downDelay: String 8 | var upBandwidth: String 9 | var upPacketLossRate: String 10 | var upDelay: String 11 | 12 | static let `default` = presets[1] 13 | 14 | static let presets = [ 15 | Profile( 16 | title: "100% Loss", 17 | downBandwidth: "0Kbit/s", downPacketLossRate: "1.0", downDelay: "0", 18 | upBandwidth: "0Kbit/s", upPacketLossRate: "1.0", upDelay: "0" 19 | ), 20 | Profile( 21 | title: "3G", 22 | downBandwidth: "780Kbit/s", downPacketLossRate: "0.0", downDelay: "100", 23 | upBandwidth: "330Kbit/s", upPacketLossRate: "0.0", upDelay: "100" 24 | ), 25 | Profile( 26 | title: "DSL", 27 | downBandwidth: "2Mbit/s", downPacketLossRate: "0.0", downDelay: "5", 28 | upBandwidth: "256Kbit/s", upPacketLossRate: "0.0", upDelay: "5" 29 | ), 30 | Profile( 31 | title: "Edge", 32 | downBandwidth: "240Kbit/s", downPacketLossRate: "0.0", downDelay: "400", 33 | upBandwidth: "200Kbit/s", upPacketLossRate: "0.0", upDelay: "440" 34 | ), 35 | Profile( 36 | title: "LTE", 37 | downBandwidth: "50Mbit/s", downPacketLossRate: "0.0", downDelay: "50", 38 | upBandwidth: "10Mbit/s", upPacketLossRate: "0.0", upDelay: "65" 39 | ), 40 | Profile( 41 | title: "Very Bad Network", 42 | downBandwidth: "1Mbit/s", downPacketLossRate: "0.1", downDelay: "500", 43 | upBandwidth: "1Mbit/s", upPacketLossRate: "0.1", upDelay: "500" 44 | ), 45 | Profile( 46 | title: "Wi-Fi", 47 | downBandwidth: "40Mbit/s", downPacketLossRate: "0.0", downDelay: "1", 48 | upBandwidth: "33Mbit/s", upPacketLossRate: "0.0", upDelay: "1" 49 | ), 50 | Profile( 51 | title: "Wi-Fi 802.11ac", 52 | downBandwidth: "250Mbit/s", downPacketLossRate: "0.0", downDelay: "1", 53 | upBandwidth: "100Mbit/s", upPacketLossRate: "0.0", upDelay: "1" 54 | ), 55 | ] 56 | } 57 | -------------------------------------------------------------------------------- /BandwidthLimiter/TrafficShaper.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct TrafficShaper { 4 | static func generateShellScript(settings: [Setting]) -> String { 5 | let profiles = Profile.presets + App.shared.appState.customProfiles 6 | 7 | let pipes = profiles.enumerated().map { (index, profile) in 8 | return 9 | #""" 10 | sudo dnctl pipe \#((index + 1) * 2 - 1) config bw "\#(profile.downBandwidth)" plr "\#(profile.downPacketLossRate)" delay "\#(profile.downDelay)" 11 | sudo dnctl pipe \#((index + 1) * 2) config bw "\#(profile.upBandwidth)" plr "\#(profile.upPacketLossRate)" delay "\#(profile.upDelay)" 12 | """# 13 | } 14 | .joined(separator: "\n") 15 | 16 | var outgoing = "" 17 | var incomming = "" 18 | for setting in settings { 19 | if setting.isActive { 20 | guard let index = profiles.firstIndex(where: { $0.title == setting.profile.title }) else { break } 21 | if let host = setting.host, !host.isEmpty { 22 | incomming += #"dummynet in from \#(host) to any pipe \#((index + 1) * 2 - 1)\n"# 23 | outgoing += #"dummynet out from any to \#(host) pipe \#((index + 1) * 2)\n"# 24 | } else { 25 | incomming += #"dummynet in from any to any pipe \#((index + 1) * 2 - 1)\n"# 26 | outgoing += #"dummynet out from any to any pipe \#((index + 1) * 2)\n"# 27 | } 28 | } 29 | } 30 | 31 | let script = """ 32 | #!/bin/bash 33 | set -e 34 | 35 | start_stop=$1 36 | 37 | if [[ $start_stop == "stop" ]]; then 38 | echo "Resetting network conditioning..." 39 | sudo dnctl -q flush 40 | sudo pfctl -f /etc/pf.conf 41 | echo "done" 42 | exit 0 43 | fi 44 | 45 | if [[ $start_stop == "start" ]]; then 46 | echo "Starting network conditioning..." 47 | (cat /etc/pf.conf && echo "dummynet-anchor \"conditioning\"" && echo "anchor \"conditioning\"") | sudo pfctl -f - 48 | 49 | \(pipes) 50 | 51 | echo "\(incomming)\(outgoing)" | sudo pfctl -a conditioning -f - 52 | set +e 53 | sudo pfctl -e 54 | echo "done" 55 | exit 0 56 | fi 57 | 58 | echo "Need to tell us whether to 'start' or 'stop' the network conditioning" 59 | exit 1 60 | """ 61 | 62 | return script 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /BandwidthLimiter/TemporaryFile.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// A wrapper around a temporary file in a temporary directory. The directory 4 | /// has been especially created for the file, so it's safe to delete when you're 5 | /// done working with the file. 6 | /// 7 | /// Call `deleteDirectory` when you no longer need the file. 8 | struct TemporaryFile { 9 | let directoryURL: URL 10 | let fileURL: URL 11 | /// Deletes the temporary directory and all files in it. 12 | let deleteDirectory: () throws -> Void 13 | 14 | /// Creates a temporary directory with a unique name and initializes the 15 | /// receiver with a `fileURL` representing a file named `filename` in that 16 | /// directory. 17 | /// 18 | /// - Note: This doesn't create the file! 19 | init(creatingTempDirectoryForFilename filename: String) throws { 20 | let (directory, deleteDirectory) = try FileManager.default 21 | .urlForUniqueTemporaryDirectory() 22 | self.directoryURL = directory 23 | self.fileURL = directory.appendingPathComponent(filename) 24 | self.deleteDirectory = deleteDirectory 25 | } 26 | } 27 | 28 | extension FileManager { 29 | /// Creates a temporary directory with a unique name and returns its URL. 30 | /// 31 | /// - Returns: A tuple of the directory's URL and a delete function. 32 | /// Call the function to delete the directory after you're done with it. 33 | /// 34 | /// - Note: You should not rely on the existence of the temporary directory 35 | /// after the app is exited. 36 | func urlForUniqueTemporaryDirectory(preferredName: String? = nil) throws 37 | -> (url: URL, deleteDirectory: () throws -> Void) 38 | { 39 | let basename = preferredName ?? UUID().uuidString 40 | 41 | var counter = 0 42 | var createdSubdirectory: URL? = nil 43 | repeat { 44 | do { 45 | let subdirName = counter == 0 ? basename : "\(basename)-\(counter)" 46 | let subdirectory = temporaryDirectory 47 | .appendingPathComponent(subdirName, isDirectory: true) 48 | try createDirectory(at: subdirectory, withIntermediateDirectories: false) 49 | createdSubdirectory = subdirectory 50 | } catch CocoaError.fileWriteFileExists { 51 | // Catch file exists error and try again with another name. 52 | // Other errors propagate to the caller. 53 | counter += 1 54 | } 55 | } while createdSubdirectory == nil 56 | 57 | let directory = createdSubdirectory! 58 | let deleteDirectory: () throws -> Void = { 59 | try self.removeItem(at: directory) 60 | } 61 | return (directory, deleteDirectory) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /com.kishikawakatsumi.BandwidthLimiter.helper/ConnectionIdentityService.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | enum ConnectionIdentityService { 4 | static private let requirementString = 5 | #"anchor apple generic and identifier "\#(HelperConstants.bundleID)" and certificate leaf[subject.OU] = "\#(HelperConstants.subject)""# as CFString 6 | 7 | static func isConnectionValid(connection: NSXPCConnection) -> Bool { 8 | guard let token = AuditTokenHack.getAuditTokenData(from: connection) else { 9 | NSLog("⚠️ Unable to get the property 'auditToken' from the connection") 10 | return true 11 | } 12 | 13 | guard let secCode = secCodeFrom(token: token) else { return false } 14 | logInfoAbout(secCode: secCode) 15 | 16 | return verifyWithRequirementString(secCode: secCode) 17 | } 18 | 19 | private static func secCodeFrom(token: Data) -> SecCode? { 20 | let attributesDict = [kSecGuestAttributeAudit: token] 21 | var secCode: SecCode? 22 | 23 | let status = SecCodeCopyGuestWithAttributes( 24 | nil, 25 | attributesDict as CFDictionary, 26 | SecCSFlags(rawValue: 0), 27 | &secCode 28 | ) 29 | 30 | if status.hasSecError { 31 | // unable to get the (running) code from the token 32 | NSLog("🛑 Could not get 'secCode' with the audit token. \(status.secErrorDescription)") 33 | return nil 34 | } 35 | 36 | return secCode 37 | } 38 | 39 | static private func verifyWithRequirementString(secCode: SecCode) -> Bool { 40 | var secRequirement: SecRequirement? 41 | 42 | let reqStatus = SecRequirementCreateWithString(requirementString, SecCSFlags(rawValue: 0), &secRequirement) 43 | if reqStatus.hasSecError { 44 | NSLog("🛑 Unable to create the requirement string. \(reqStatus.secErrorDescription)") 45 | return false 46 | } 47 | 48 | 49 | let validityStatus = SecCodeCheckValidity(secCode, SecCSFlags(rawValue: 0), secRequirement) 50 | if validityStatus.hasSecError { 51 | NSLog("🛑 NSXPC client does not meet the requirements. \(validityStatus.secErrorDescription)") 52 | return false 53 | } 54 | 55 | return true 56 | } 57 | 58 | private static func logInfoAbout(secCode: SecCode) { 59 | var secStaticCode: SecStaticCode? 60 | var cfDictionary: CFDictionary? 61 | 62 | SecCodeCopyStaticCode(secCode, SecCSFlags(rawValue: 0), &secStaticCode) 63 | 64 | guard let staticCode = secStaticCode else { 65 | NSLog("Unable to copy the signature of the running app") 66 | return 67 | } 68 | 69 | let copyStatus = SecCodeCopySigningInformation(staticCode, SecCSFlags(rawValue: 0), &cfDictionary) 70 | 71 | if copyStatus.hasSecError { 72 | NSLog("⚠️ Unable to get info about connection. \(copyStatus.secErrorDescription)") 73 | } else if let dict = cfDictionary { 74 | let dict = dict as NSDictionary 75 | let info = dict["info-plist"] as? NSDictionary 76 | let bundleIdAny = info?["CFBundleIdentifier"] ?? "Unknown" 77 | let bundleId = String(describing: bundleIdAny) 78 | NSLog("Received connection request from app with bundle ID '\(bundleId)'") 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### https://raw.github.com/github/gitignore/218a941be92679ce67d0484547e3e142b2f5f6f0/Swift.gitignore 2 | 3 | # Xcode 4 | # 5 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 6 | 7 | ## User settings 8 | xcuserdata/ 9 | 10 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 11 | *.xcscmblueprint 12 | *.xccheckout 13 | 14 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 15 | build/ 16 | DerivedData/ 17 | *.moved-aside 18 | *.pbxuser 19 | !default.pbxuser 20 | *.mode1v3 21 | !default.mode1v3 22 | *.mode2v3 23 | !default.mode2v3 24 | *.perspectivev3 25 | !default.perspectivev3 26 | 27 | ## Obj-C/Swift specific 28 | *.hmap 29 | 30 | ## App packaging 31 | *.ipa 32 | *.dSYM.zip 33 | *.dSYM 34 | 35 | ## Playgrounds 36 | timeline.xctimeline 37 | playground.xcworkspace 38 | 39 | # Swift Package Manager 40 | # 41 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 42 | # Packages/ 43 | # Package.pins 44 | # Package.resolved 45 | # *.xcodeproj 46 | # 47 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 48 | # hence it is not needed unless you have added a package configuration file to your project 49 | # .swiftpm 50 | 51 | .build/ 52 | 53 | # CocoaPods 54 | # 55 | # We recommend against adding the Pods directory to your .gitignore. However 56 | # you should judge for yourself, the pros and cons are mentioned at: 57 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 58 | # 59 | # Pods/ 60 | # 61 | # Add this line if you want to avoid checking in source code from the Xcode workspace 62 | # *.xcworkspace 63 | 64 | # Carthage 65 | # 66 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 67 | # Carthage/Checkouts 68 | 69 | Carthage/Build/ 70 | 71 | # Accio dependency management 72 | Dependencies/ 73 | .accio/ 74 | 75 | # fastlane 76 | # 77 | # It is recommended to not store the screenshots in the git repo. 78 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 79 | # For more information about the recommended setup visit: 80 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 81 | 82 | fastlane/report.xml 83 | fastlane/Preview.html 84 | fastlane/screenshots/**/*.png 85 | fastlane/test_output 86 | 87 | # Code Injection 88 | # 89 | # After new code Injection tools there's a generated folder /iOSInjectionProject 90 | # https://github.com/johnno1962/injectionforxcode 91 | 92 | iOSInjectionProject/ 93 | 94 | 95 | ### https://raw.github.com/github/gitignore/218a941be92679ce67d0484547e3e142b2f5f6f0/Global/macOS.gitignore 96 | 97 | # General 98 | .DS_Store 99 | .AppleDouble 100 | .LSOverride 101 | 102 | # Icon must end with two \r 103 | Icon 104 | 105 | # Thumbnails 106 | ._* 107 | 108 | # Files that might appear in the root of a volume 109 | .DocumentRevisions-V100 110 | .fseventsd 111 | .Spotlight-V100 112 | .TemporaryItems 113 | .Trashes 114 | .VolumeIcon.icns 115 | .com.apple.timemachine.donotpresent 116 | 117 | # Directories potentially created on remote AFP share 118 | .AppleDB 119 | .AppleDesktop 120 | Network Trash Folder 121 | Temporary Items 122 | .apdisk 123 | 124 | 125 | -------------------------------------------------------------------------------- /BandwidthLimiter/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import Combine 3 | 4 | @main 5 | class AppDelegate: NSObject, NSApplicationDelegate { 6 | private var statusItem: NSStatusItem? 7 | private let toggleStatusMenuItem = NSMenuItem(title: "Enable Bandwidth Limiter", action: #selector(toggleLimiterStatus), keyEquivalent: "") 8 | private let settingsWindowController = NSStoryboard(name: "SettingsWindow", bundle: nil).instantiateInitialController() as? NSWindowController 9 | 10 | private var cancellables = [AnyCancellable]() 11 | 12 | func applicationDidFinishLaunching(_ aNotification: Notification) { 13 | statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength) 14 | configureStatusItem(statusItem) 15 | 16 | let menu = NSMenu() 17 | 18 | menu.addItem(toggleStatusMenuItem) 19 | menu.addItem(NSMenuItem(title: "Configure...", action: #selector(openConfigurationWindow), keyEquivalent: "")) 20 | menu.addItem(NSMenuItem.separator()) 21 | menu.addItem(NSMenuItem(title: "Quit Bandwidth Limiter", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q")) 22 | 23 | statusItem?.menu = menu 24 | 25 | ExecutionServiceProxy().checkForUpdates { (needsUpdate) in 26 | if needsUpdate { 27 | try? ExecutionServiceProxy().installHelper() 28 | } 29 | } 30 | 31 | App.shared.$appState 32 | .removeDuplicates() 33 | .receive(on: DispatchQueue.main) 34 | .sink { [weak self] (appState) in 35 | guard let self = self else { return } 36 | self.configureStatusItem(self.statusItem) 37 | } 38 | .store(in: &cancellables) 39 | } 40 | 41 | func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply { 42 | guard ExecutionService.isHelperInstalled else { 43 | return .terminateNow 44 | } 45 | 46 | do { 47 | let tempFile = try TemporaryFile(creatingTempDirectoryForFilename: "network.sh") 48 | let script = TrafficShaper.generateShellScript(settings: []) 49 | guard let data = script.data(using: .utf8) else { return .terminateNow } 50 | try data.write(to: tempFile.fileURL) 51 | 52 | try ExecutionService.shared.executeScript(at: tempFile.fileURL.path, options: ["stop"]) { (result) in 53 | switch result { 54 | case .success(let output): 55 | print(output) 56 | case .failure(let error): 57 | print(error) 58 | } 59 | 60 | DispatchQueue.main.async { 61 | sender.reply(toApplicationShouldTerminate: true) 62 | } 63 | } 64 | } catch { 65 | print(error) 66 | sender.reply(toApplicationShouldTerminate: true) 67 | } 68 | 69 | return .terminateLater 70 | } 71 | 72 | private func configureStatusItem(_ statusItem: NSStatusItem?) { 73 | let isActive = App.shared.appState.isActive 74 | 75 | toggleStatusMenuItem.title = isActive ? "Disable Bandwidth Limiter" : "Enable Bandwidth Limiter" 76 | statusItem?.button?.image = isActive ? NSImage(named: "tachometer-slow-solid") : NSImage(named: "tachometer-fast-regular") 77 | statusItem?.button?.imagePosition = .imageLeft 78 | } 79 | 80 | @objc 81 | private func toggleLimiterStatus() { 82 | App.shared.appState.isActive.toggle() 83 | configureStatusItem(statusItem) 84 | } 85 | 86 | @objc 87 | private func openConfigurationWindow() { 88 | NSApp.activate(ignoringOtherApps: true) 89 | settingsWindowController?.showWindow(self) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /BandwidthLimiter/ExecutionServiceProxy.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import SecurityFoundation 3 | import ServiceManagement 4 | 5 | class ExecutionServiceProxy { 6 | private static var isHelperInstalled: Bool { ExecutionService.isHelperInstalled } 7 | 8 | func getProxy() throws -> HelperProtocol { 9 | var proxyError: Error? 10 | let helper = try getConnection().remoteObjectProxyWithErrorHandler { (error) in proxyError = error } as? HelperProtocol 11 | if let unwrappedHelper = helper { 12 | return unwrappedHelper 13 | } else { 14 | throw AuthorizationError.helperConnection(proxyError?.localizedDescription ?? "Unknown error") 15 | } 16 | } 17 | 18 | func checkForUpdates(completion: @escaping (Bool) -> Void) { 19 | let helerUrl = Bundle.main.bundleURL.appendingPathComponent("Contents/Library/LaunchServices/" + HelperConstants.domain) 20 | guard let helperBundleInfo = CFBundleCopyInfoDictionaryForURL(helerUrl as CFURL) as? [String: Any], 21 | let helperVersion = helperBundleInfo["CFBundleShortVersionString"] as? String, 22 | let proxy = try? getProxy() else { 23 | completion(true) 24 | return 25 | } 26 | 27 | proxy.getVersion { (installedHelperVersion) in 28 | completion(installedHelperVersion != helperVersion) 29 | } 30 | } 31 | 32 | func installHelper() throws { 33 | var authRef: AuthorizationRef? 34 | var authStatus = AuthorizationCreate(nil, nil, [.preAuthorize], &authRef) 35 | 36 | guard authStatus == errAuthorizationSuccess else { 37 | throw AuthorizationError.helperInstallation("Unable to get a valid empty authorization reference to load Helper daemon") 38 | } 39 | 40 | let authItem = kSMRightBlessPrivilegedHelper.withCString { authorizationString in 41 | AuthorizationItem(name: authorizationString, valueLength: 0, value: nil, flags: 0) 42 | } 43 | 44 | let pointer = UnsafeMutablePointer.allocate(capacity: 1) 45 | pointer.initialize(to: authItem) 46 | 47 | defer { 48 | pointer.deinitialize(count: 1) 49 | pointer.deallocate() 50 | } 51 | 52 | var authRights = AuthorizationRights(count: 1, items: pointer) 53 | 54 | let flags: AuthorizationFlags = [.interactionAllowed, .extendRights, .preAuthorize] 55 | authStatus = AuthorizationCreate(&authRights, nil, flags, &authRef) 56 | 57 | guard authStatus == errAuthorizationSuccess else { 58 | throw AuthorizationError.helperInstallation("Unable to get a valid loading authorization reference to load Helper daemon") 59 | } 60 | 61 | var error: Unmanaged? 62 | if SMJobBless(kSMDomainSystemLaunchd, HelperConstants.domain as CFString, authRef, &error) == false { 63 | let blessError = error!.takeRetainedValue() as Error 64 | throw AuthorizationError.helperInstallation("Error while installing the Helper: \(blessError.localizedDescription)") 65 | } 66 | 67 | AuthorizationFree(authRef!, []) 68 | } 69 | 70 | private func getConnection() throws -> NSXPCConnection { 71 | if !Self.isHelperInstalled { 72 | try installHelper() 73 | } 74 | return createConnection() 75 | } 76 | 77 | private func createConnection() -> NSXPCConnection { 78 | let connection = NSXPCConnection(machServiceName: HelperConstants.domain, options: .privileged) 79 | connection.remoteObjectInterface = NSXPCInterface(with: HelperProtocol.self) 80 | connection.exportedInterface = NSXPCInterface(with: RemoteApplicationProtocol.self) 81 | connection.exportedObject = self 82 | 83 | connection.invalidationHandler = { 84 | if Self.isHelperInstalled { 85 | print("Unable to connect to Helper although it is installed") 86 | } else { 87 | print("Helper is not installed") 88 | } 89 | } 90 | connection.interruptionHandler = { [weak self] in 91 | try? self?.installHelper() 92 | } 93 | 94 | connection.resume() 95 | 96 | return connection 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /BandwidthLimiter/SettingsViewController.swift: -------------------------------------------------------------------------------- 1 | import AppKit 2 | import Combine 3 | 4 | class SettingsViewController: NSViewController { 5 | @IBOutlet var statusLabel: NSTextField! 6 | @IBOutlet var statusImageView: NSImageView! 7 | @IBOutlet var toggleStatusButton: NSButton! 8 | 9 | @IBOutlet var tableView: NSTableView! 10 | @IBOutlet var addRemoveButton: NSSegmentedControl! 11 | 12 | private var cancellables = [AnyCancellable]() 13 | 14 | override func viewDidLoad() { 15 | super.viewDidLoad() 16 | 17 | App.shared.$appState 18 | .removeDuplicates() 19 | .receive(on: DispatchQueue.main) 20 | .sink { [weak self] (appState) in 21 | self?.toggleStatusButton.isEnabled = false 22 | self?.tableView.reloadData() 23 | 24 | do { 25 | let tempFile = try TemporaryFile(creatingTempDirectoryForFilename: "network.sh") 26 | let script = TrafficShaper.generateShellScript(settings: appState.settings) 27 | guard let data = script.data(using: .utf8) else { return } 28 | try data.write(to: tempFile.fileURL) 29 | 30 | try ExecutionService.shared.executeScript(at: tempFile.fileURL.path, options: [appState.isActive ? "start" : "stop"]) { [weak self] (result) in 31 | switch result { 32 | case .success(let output): 33 | print(output) 34 | case .failure(let error): 35 | let alert = NSAlert(error: error) 36 | alert.runModal() 37 | } 38 | 39 | DispatchQueue.main.async { [weak self] in 40 | self?.statusLabel.objectValue = appState.isActive ? "On" : "Off" 41 | self?.statusImageView.image = appState.isActive ? NSImage(named: NSImage.statusAvailableName) : NSImage(named: NSImage.statusNoneName) 42 | self?.toggleStatusButton.title = "\(appState.isActive ? "Disable" : "Enable") Bandwidth Limitter" 43 | self?.toggleStatusButton.isEnabled = true 44 | } 45 | } 46 | } catch { 47 | let alert = NSAlert(error: error) 48 | alert.runModal() 49 | } 50 | } 51 | .store(in: &cancellables) 52 | } 53 | 54 | @IBAction 55 | func addOrRemoveRow(_ sender: NSSegmentedControl) { 56 | var settings = App.shared.appState.settings 57 | 58 | switch sender.indexOfSelectedItem { 59 | case 0: 60 | settings.append(Setting(host: nil, port: nil, profile: .default, isActive: false)) 61 | case 1: 62 | if tableView.selectedRow != -1 { 63 | settings.remove(at: tableView.selectedRow) 64 | } 65 | default: 66 | break 67 | } 68 | 69 | App.shared.appState.settings = settings 70 | tableView.reloadData() 71 | } 72 | } 73 | 74 | extension SettingsViewController: NSTableViewDataSource { 75 | func numberOfRows(in tableView: NSTableView) -> Int { 76 | let settings = App.shared.appState.settings 77 | return settings.count 78 | } 79 | } 80 | 81 | extension SettingsViewController: NSTableViewDelegate { 82 | func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? { 83 | let settings = App.shared.appState.settings 84 | 85 | switch tableColumn?.title { 86 | case "Host": 87 | return settings[row].host 88 | case "Port": 89 | return settings[row].port 90 | case "Profile": 91 | let index = (Profile.presets + App.shared.appState.customProfiles).firstIndex { $0.title == settings[row].profile.title } 92 | return index ?? 0 93 | case "Active": 94 | return settings[row].isActive 95 | default: 96 | return nil 97 | } 98 | } 99 | 100 | func tableView(_ tableView: NSTableView, setObjectValue object: Any?, for tableColumn: NSTableColumn?, row: Int) { 101 | var settings = App.shared.appState.settings 102 | 103 | switch tableColumn?.title { 104 | case "Host": 105 | if let object = object { 106 | settings[row].host = "\(object)" 107 | } else { 108 | settings[row].host = nil 109 | } 110 | case "Port": 111 | if let object = object { 112 | settings[row].port = "\(object)" 113 | } else { 114 | settings[row].port = nil 115 | } 116 | case "Profile": 117 | if let index = object as? Int { 118 | let profiles = Profile.presets + App.shared.appState.customProfiles 119 | if index == -1 { 120 | settings[row].profile = Profile.presets[0] 121 | } else if index < profiles.count { 122 | settings[row].profile = profiles[index] 123 | } else { 124 | settings[row].profile = Profile.presets[0] 125 | } 126 | } 127 | case "Active": 128 | if let object = object as? Bool { 129 | settings[row].isActive = object 130 | } else { 131 | settings[row].isActive = false 132 | } 133 | default: 134 | break 135 | } 136 | 137 | App.shared.appState.settings = settings 138 | } 139 | 140 | func tableView(_ tableView: NSTableView, dataCellFor tableColumn: NSTableColumn?, row: Int) -> NSCell? { 141 | if let tableColumn = tableColumn, tableColumn.identifier.rawValue == "Profile" { 142 | if let dataCell = tableColumn.dataCell(forRow: row) as? NSPopUpButtonCell { 143 | dataCell.removeAllItems() 144 | dataCell.addItems(withTitles: Profile.presets.map { $0.title }) 145 | App.shared.appState.customProfiles.forEach { (profile) in 146 | dataCell.addItem(withTitle: profile.title) 147 | } 148 | return dataCell 149 | } 150 | } 151 | return nil 152 | } 153 | 154 | func tableViewSelectionDidChange(_ notification: Notification) { 155 | addRemoveButton.setEnabled(tableView.selectedRow != -1, forSegment: 1) 156 | } 157 | 158 | @IBAction 159 | private func toggleLimiterStatus(_ sender: NSButton) { 160 | App.shared.appState.isActive.toggle() 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /BandwidthLimiter/ManageProfilesViewController.swift: -------------------------------------------------------------------------------- 1 | import AppKit 2 | import Combine 3 | 4 | class ManageProfilesViewController: NSViewController { 5 | @IBOutlet var outlineView: NSOutlineView! 6 | @IBOutlet var addRemoveButton: NSSegmentedControl! 7 | 8 | @IBOutlet var downBandwidthField: NSTextField! 9 | @IBOutlet var downBandwidthUnitButton: NSPopUpButton! 10 | @IBOutlet var downPacketLossRateField: NSTextField! 11 | @IBOutlet var downDelayField: NSTextField! 12 | 13 | @IBOutlet var upBandwidthField: NSTextField! 14 | @IBOutlet var upBandwidthUnitButton: NSPopUpButton! 15 | @IBOutlet var upPacketLossRateField: NSTextField! 16 | @IBOutlet var upDelayField: NSTextField! 17 | 18 | private var temporaryAppState = App.shared.appState 19 | 20 | private var cancellables = [AnyCancellable]() 21 | 22 | override func viewDidLoad() { 23 | super.viewDidLoad() 24 | outlineView.expandItem(nil, expandChildren: true) 25 | outlineView.selectRowIndexes([1], byExtendingSelection: false) 26 | } 27 | 28 | private func updateCurrentCustomProfile() { 29 | let selectedRow = outlineView.selectedRow 30 | guard selectedRow > Profile.presets.count + 1 else { 31 | return 32 | } 33 | 34 | if var item = outlineView.item(atRow: selectedRow) as? Profile { 35 | if downBandwidthUnitButton.indexOfSelectedItem == 0 { 36 | item.downBandwidth = "\(downBandwidthField.stringValue)Kbit/s" 37 | } else { 38 | item.downBandwidth = "\(downBandwidthField.stringValue)Mbit/s" 39 | } 40 | item.downPacketLossRate = String(format: "%.2f", ((Double(downPacketLossRateField.stringValue) ?? 0) * 100)) 41 | item.downDelay = downDelayField.stringValue 42 | 43 | if upBandwidthUnitButton.indexOfSelectedItem == 0 { 44 | item.upBandwidth = "\(upBandwidthField.stringValue)Kbit/s" 45 | } else { 46 | item.upBandwidth = "\(upBandwidthField.stringValue)Mbit/s" 47 | } 48 | item.downPacketLossRate = String(format: "%.2f", ((Double(upPacketLossRateField.stringValue) ?? 0) * 100)) 49 | item.downDelay = upDelayField.stringValue 50 | 51 | if selectedRow != -1 { 52 | temporaryAppState.customProfiles[outlineView.selectedRow - 2 - Profile.presets.count] = item 53 | 54 | outlineView.reloadData() 55 | outlineView.selectRowIndexes([selectedRow], byExtendingSelection: false) 56 | } 57 | } 58 | } 59 | 60 | @IBAction 61 | func addOrRemoveProfile(_ sender: NSSegmentedControl) { 62 | var customProfiles = temporaryAppState.customProfiles 63 | 64 | switch sender.indexOfSelectedItem { 65 | case 0: 66 | if let viewController = storyboard?.instantiateController(withIdentifier: "ProrileNameInput") as? NewProfileNameViewController { 67 | viewController.temporaryAppState = temporaryAppState 68 | viewController.profileAdded 69 | .sink { [weak self] (appState) in 70 | self?.temporaryAppState = appState 71 | self?.outlineView.reloadData() 72 | } 73 | .store(in: &cancellables) 74 | 75 | presentAsSheet(viewController) 76 | } 77 | return 78 | case 1: 79 | if outlineView.selectedRow != -1 { 80 | customProfiles.remove(at: outlineView.selectedRow - 2 - Profile.presets.count) 81 | } 82 | default: 83 | break 84 | } 85 | 86 | temporaryAppState.customProfiles = customProfiles 87 | outlineView.reloadData() 88 | } 89 | 90 | @IBAction 91 | func editingChanged(_ sender: NSControl) { 92 | updateCurrentCustomProfile() 93 | } 94 | 95 | @IBAction 96 | func save(_ sender: NSButton) { 97 | updateCurrentCustomProfile() 98 | 99 | for (index, setting) in temporaryAppState.settings.enumerated() { 100 | let profiles = Profile.presets + temporaryAppState.customProfiles 101 | let profileNames = profiles.map { $0.title } 102 | if !profileNames.contains(setting.profile.title) { 103 | temporaryAppState.settings[index].profile = Profile.presets[0] 104 | } 105 | } 106 | App.shared.appState = temporaryAppState 107 | dismiss(self) 108 | } 109 | 110 | @IBAction 111 | func cancel(_ sender: NSButton) { 112 | dismiss(self) 113 | } 114 | } 115 | 116 | extension ManageProfilesViewController: NSOutlineViewDataSource { 117 | func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int { 118 | if let item = item as? String, item == "Preset Profiles" { 119 | return Profile.presets.count 120 | } 121 | if let item = item as? String, item == "Custom Profiles" { 122 | return temporaryAppState.customProfiles.count 123 | } 124 | return 2 125 | } 126 | 127 | func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any { 128 | if let item = item as? String, item == "Preset Profiles" { 129 | return Profile.presets[index] 130 | } 131 | if let item = item as? String, item == "Custom Profiles" { 132 | return temporaryAppState.customProfiles[index] 133 | } 134 | 135 | switch index { 136 | case 0: 137 | return "Preset Profiles" 138 | case 1: 139 | return "Custom Profiles" 140 | default: 141 | return "" 142 | } 143 | } 144 | 145 | func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool { 146 | if let _ = item as? String { 147 | return true 148 | } 149 | return false 150 | } 151 | } 152 | 153 | extension ManageProfilesViewController: NSOutlineViewDelegate { 154 | func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? { 155 | let cellIdentifier = NSUserInterfaceItemIdentifier("cell") 156 | let cell = outlineView.makeView(withIdentifier: cellIdentifier, owner: self) as! NSTableCellView 157 | 158 | if let item = item as? String { 159 | cell.textField!.stringValue = item 160 | let fontDescriptor: NSFontDescriptor 161 | if #available(macOS 11.0, *) { 162 | fontDescriptor = NSFont.preferredFont(forTextStyle: .callout, options: [:]).fontDescriptor.withSymbolicTraits(.bold) 163 | } else { 164 | fontDescriptor = NSFont.boldSystemFont(ofSize: 12).fontDescriptor 165 | } 166 | cell.textField?.font = NSFont(descriptor: fontDescriptor, size: fontDescriptor.pointSize) 167 | cell.textField?.textColor = NSColor.secondaryLabelColor 168 | } 169 | if let item = item as? Profile { 170 | cell.textField!.stringValue = item.title 171 | if #available(macOS 11.0, *) { 172 | cell.textField?.font = NSFont.preferredFont(forTextStyle: .body, options: [:]) 173 | } else { 174 | cell.textField?.font = NSFont.systemFont(ofSize: 13) 175 | } 176 | cell.textField?.textColor = NSColor.labelColor 177 | } 178 | 179 | return cell 180 | } 181 | 182 | func outlineView(_ outlineView: NSOutlineView, shouldSelectItem item: Any) -> Bool { 183 | if let _ = item as? String { 184 | return false 185 | } 186 | return true 187 | } 188 | 189 | func outlineViewSelectionDidChange(_ notification: Notification) { 190 | if let item = outlineView.item(atRow: outlineView.selectedRow) as? Profile { 191 | if let parent = outlineView.parent(forItem: item) as? String, parent == "Preset Profiles" { 192 | downBandwidthField.isEnabled = false 193 | downBandwidthUnitButton.isEnabled = false 194 | downPacketLossRateField.isEnabled = false 195 | downDelayField.isEnabled = false 196 | 197 | upBandwidthField.isEnabled = false 198 | upBandwidthUnitButton.isEnabled = false 199 | upPacketLossRateField.isEnabled = false 200 | upDelayField.isEnabled = false 201 | 202 | addRemoveButton.setEnabled(false, forSegment: 1) 203 | } else { 204 | downBandwidthField.isEnabled = true 205 | downBandwidthUnitButton.isEnabled = true 206 | downPacketLossRateField.isEnabled = true 207 | downDelayField.isEnabled = true 208 | 209 | upBandwidthField.isEnabled = true 210 | upBandwidthUnitButton.isEnabled = true 211 | upPacketLossRateField.isEnabled = true 212 | upDelayField.isEnabled = true 213 | 214 | addRemoveButton.setEnabled(true, forSegment: 1) 215 | } 216 | 217 | if item.downBandwidth.hasSuffix("Kbit/s") { 218 | downBandwidthField.stringValue = item.downBandwidth.replacingOccurrences(of: "Kbit/s", with: "") 219 | downBandwidthUnitButton.selectItem(at: 0) 220 | } 221 | if item.downBandwidth.hasSuffix("Mbit/s") { 222 | downBandwidthField.stringValue = item.downBandwidth.replacingOccurrences(of: "Mbit/s", with: "") 223 | downBandwidthUnitButton.selectItem(at: 1) 224 | } 225 | downPacketLossRateField.stringValue = "\(Int(100 * (Double(item.downPacketLossRate) ?? 0)))" 226 | downDelayField.stringValue = item.downDelay 227 | 228 | if item.upBandwidth.hasSuffix("Kbit/s") { 229 | upBandwidthField.stringValue = item.upBandwidth.replacingOccurrences(of: "Kbit/s", with: "") 230 | upBandwidthUnitButton.selectItem(at: 0) 231 | } 232 | if item.upBandwidth.hasSuffix("Mbit/s") { 233 | upBandwidthField.stringValue = item.upBandwidth.replacingOccurrences(of: "Mbit/s", with: "") 234 | upBandwidthUnitButton.selectItem(at: 1) 235 | } 236 | upPacketLossRateField.stringValue = "\(Int(100 * (Double(item.downPacketLossRate) ?? 0)))" 237 | upDelayField.stringValue = item.upDelay 238 | } 239 | 240 | if outlineView.selectedRow == -1 { 241 | addRemoveButton.setEnabled(false, forSegment: 1) 242 | } 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /BandwidthLimiter.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 084F51FD2640C4C400B05BFC /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 084F51FC2640C4C400B05BFC /* AppDelegate.swift */; }; 11 | 084F52012640C4C400B05BFC /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 084F52002640C4C400B05BFC /* Assets.xcassets */; }; 12 | 084F52042640C4C400B05BFC /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 084F52022640C4C400B05BFC /* Main.storyboard */; }; 13 | 084F52132640C4EA00B05BFC /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 084F52122640C4EA00B05BFC /* main.swift */; }; 14 | 084F521A2640C52900B05BFC /* com.kishikawakatsumi.BandwidthLimiter.helper in CopyFiles */ = {isa = PBXBuildFile; fileRef = 084F52102640C4EA00B05BFC /* com.kishikawakatsumi.BandwidthLimiter.helper */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; 15 | 084F521E2640C70400B05BFC /* Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 084F521D2640C70400B05BFC /* Helper.swift */; }; 16 | 084F52202640C73800B05BFC /* HelperConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 084F521F2640C73800B05BFC /* HelperConstants.swift */; }; 17 | 084F52222640C77400B05BFC /* HelperExecutionService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 084F52212640C77400B05BFC /* HelperExecutionService.swift */; }; 18 | 084F52262640C7D200B05BFC /* HelperProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 084F52252640C7D200B05BFC /* HelperProtocol.swift */; }; 19 | 084F52282640C80A00B05BFC /* Result+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 084F52272640C80A00B05BFC /* Result+Extensions.swift */; }; 20 | 084F522A2640C83600B05BFC /* ConnectionIdentityService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 084F52292640C83600B05BFC /* ConnectionIdentityService.swift */; }; 21 | 084F522C2640C87900B05BFC /* ExecutionService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 084F522B2640C87900B05BFC /* ExecutionService.swift */; }; 22 | 084F52312640C8C000B05BFC /* Result+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 084F52272640C80A00B05BFC /* Result+Extensions.swift */; }; 23 | 084F52322640C8C000B05BFC /* HelperProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 084F52252640C7D200B05BFC /* HelperProtocol.swift */; }; 24 | 084F52332640C8CE00B05BFC /* HelperConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 084F521F2640C73800B05BFC /* HelperConstants.swift */; }; 25 | 084F52352641466F00B05BFC /* SettingsWindow.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 084F52342641466F00B05BFC /* SettingsWindow.storyboard */; }; 26 | 084F52372641468F00B05BFC /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 084F52362641468F00B05BFC /* SettingsViewController.swift */; }; 27 | 084F523B26415FA100B05BFC /* Profile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 084F523A26415FA100B05BFC /* Profile.swift */; }; 28 | 084F523D2641685C00B05BFC /* Setting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 084F523C2641685C00B05BFC /* Setting.swift */; }; 29 | 084F52412641810D00B05BFC /* ExecutionServiceProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 084F52402641810D00B05BFC /* ExecutionServiceProxy.swift */; }; 30 | 084F52442641819400B05BFC /* RemoteApplicationProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 084F52432641819400B05BFC /* RemoteApplicationProtocol.swift */; }; 31 | 084F52452641832D00B05BFC /* RemoteApplicationProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 084F52432641819400B05BFC /* RemoteApplicationProtocol.swift */; }; 32 | 084F5247264183EE00B05BFC /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 084F5246264183EE00B05BFC /* Errors.swift */; }; 33 | 084F52482641898400B05BFC /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 084F5246264183EE00B05BFC /* Errors.swift */; }; 34 | 084F524A26419A7700B05BFC /* AppState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 084F524926419A7700B05BFC /* AppState.swift */; }; 35 | 084F524C26419BF000B05BFC /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 084F524B26419BF000B05BFC /* App.swift */; }; 36 | 084F52522641B6CF00B05BFC /* TrafficShaper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 084F52512641B6CF00B05BFC /* TrafficShaper.swift */; }; 37 | 084F52542641BCF400B05BFC /* TemporaryFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 084F52532641BCF400B05BFC /* TemporaryFile.swift */; }; 38 | 084F52562641DDB100B05BFC /* TextFieldCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 084F52552641DDB100B05BFC /* TextFieldCell.swift */; }; 39 | 085B23B72642963F00C91993 /* AuditTokenHack.m in Sources */ = {isa = PBXBuildFile; fileRef = 085B23B62642963F00C91993 /* AuditTokenHack.m */; }; 40 | 085B23B92642967300C91993 /* OSStatus+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 085B23B82642967300C91993 /* OSStatus+Extensions.swift */; }; 41 | 0872DEBE264253F200E651E2 /* ManageProfilesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0872DEBD264253F200E651E2 /* ManageProfilesViewController.swift */; }; 42 | 0872DEC0264272D200E651E2 /* NewProfileNameViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0872DEBF264272D200E651E2 /* NewProfileNameViewController.swift */; }; 43 | /* End PBXBuildFile section */ 44 | 45 | /* Begin PBXContainerItemProxy section */ 46 | 084F52172640C50100B05BFC /* PBXContainerItemProxy */ = { 47 | isa = PBXContainerItemProxy; 48 | containerPortal = 084F51F12640C4C400B05BFC /* Project object */; 49 | proxyType = 1; 50 | remoteGlobalIDString = 084F520F2640C4EA00B05BFC; 51 | remoteInfo = com.kishikawakatsumi.BandwidthLimiter.helper; 52 | }; 53 | /* End PBXContainerItemProxy section */ 54 | 55 | /* Begin PBXCopyFilesBuildPhase section */ 56 | 084F520E2640C4EA00B05BFC /* CopyFiles */ = { 57 | isa = PBXCopyFilesBuildPhase; 58 | buildActionMask = 2147483647; 59 | dstPath = /usr/share/man/man1/; 60 | dstSubfolderSpec = 0; 61 | files = ( 62 | ); 63 | runOnlyForDeploymentPostprocessing = 1; 64 | }; 65 | 084F52192640C52000B05BFC /* CopyFiles */ = { 66 | isa = PBXCopyFilesBuildPhase; 67 | buildActionMask = 2147483647; 68 | dstPath = Contents/Library/LaunchServices; 69 | dstSubfolderSpec = 1; 70 | files = ( 71 | 084F521A2640C52900B05BFC /* com.kishikawakatsumi.BandwidthLimiter.helper in CopyFiles */, 72 | ); 73 | runOnlyForDeploymentPostprocessing = 0; 74 | }; 75 | /* End PBXCopyFilesBuildPhase section */ 76 | 77 | /* Begin PBXFileReference section */ 78 | 084F51F92640C4C400B05BFC /* Bandwidth Limiter.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Bandwidth Limiter.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 79 | 084F51FC2640C4C400B05BFC /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 80 | 084F52002640C4C400B05BFC /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 81 | 084F52032640C4C400B05BFC /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 82 | 084F52052640C4C400B05BFC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 83 | 084F52062640C4C400B05BFC /* BandwidthLimiter.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = BandwidthLimiter.entitlements; sourceTree = ""; }; 84 | 084F52102640C4EA00B05BFC /* com.kishikawakatsumi.BandwidthLimiter.helper */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = com.kishikawakatsumi.BandwidthLimiter.helper; sourceTree = BUILT_PRODUCTS_DIR; }; 85 | 084F52122640C4EA00B05BFC /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; 86 | 084F521B2640C57600B05BFC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 87 | 084F521C2640C60600B05BFC /* launchd.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = launchd.plist; sourceTree = ""; }; 88 | 084F521D2640C70400B05BFC /* Helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Helper.swift; sourceTree = ""; }; 89 | 084F521F2640C73800B05BFC /* HelperConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HelperConstants.swift; sourceTree = ""; }; 90 | 084F52212640C77400B05BFC /* HelperExecutionService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HelperExecutionService.swift; sourceTree = ""; }; 91 | 084F52252640C7D200B05BFC /* HelperProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HelperProtocol.swift; sourceTree = ""; }; 92 | 084F52272640C80A00B05BFC /* Result+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Result+Extensions.swift"; sourceTree = ""; }; 93 | 084F52292640C83600B05BFC /* ConnectionIdentityService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionIdentityService.swift; sourceTree = ""; }; 94 | 084F522B2640C87900B05BFC /* ExecutionService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExecutionService.swift; sourceTree = ""; }; 95 | 084F52342641466F00B05BFC /* SettingsWindow.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = SettingsWindow.storyboard; sourceTree = ""; }; 96 | 084F52362641468F00B05BFC /* SettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = ""; }; 97 | 084F523A26415FA100B05BFC /* Profile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Profile.swift; sourceTree = ""; }; 98 | 084F523C2641685C00B05BFC /* Setting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Setting.swift; sourceTree = ""; }; 99 | 084F52402641810D00B05BFC /* ExecutionServiceProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExecutionServiceProxy.swift; sourceTree = ""; }; 100 | 084F52432641819400B05BFC /* RemoteApplicationProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteApplicationProtocol.swift; sourceTree = ""; }; 101 | 084F5246264183EE00B05BFC /* Errors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Errors.swift; sourceTree = ""; }; 102 | 084F524926419A7700B05BFC /* AppState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppState.swift; sourceTree = ""; }; 103 | 084F524B26419BF000B05BFC /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = ""; }; 104 | 084F52512641B6CF00B05BFC /* TrafficShaper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrafficShaper.swift; sourceTree = ""; }; 105 | 084F52532641BCF400B05BFC /* TemporaryFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemporaryFile.swift; sourceTree = ""; }; 106 | 084F52552641DDB100B05BFC /* TextFieldCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldCell.swift; sourceTree = ""; }; 107 | 085B23B42642963E00C91993 /* com.kishikawakatsumi.BandwidthLimiter.helper-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "com.kishikawakatsumi.BandwidthLimiter.helper-Bridging-Header.h"; sourceTree = ""; }; 108 | 085B23B52642963F00C91993 /* AuditTokenHack.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AuditTokenHack.h; sourceTree = ""; }; 109 | 085B23B62642963F00C91993 /* AuditTokenHack.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AuditTokenHack.m; sourceTree = ""; }; 110 | 085B23B82642967300C91993 /* OSStatus+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OSStatus+Extensions.swift"; sourceTree = ""; }; 111 | 0872DEBD264253F200E651E2 /* ManageProfilesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManageProfilesViewController.swift; sourceTree = ""; }; 112 | 0872DEBF264272D200E651E2 /* NewProfileNameViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewProfileNameViewController.swift; sourceTree = ""; }; 113 | /* End PBXFileReference section */ 114 | 115 | /* Begin PBXFrameworksBuildPhase section */ 116 | 084F51F62640C4C400B05BFC /* Frameworks */ = { 117 | isa = PBXFrameworksBuildPhase; 118 | buildActionMask = 2147483647; 119 | files = ( 120 | ); 121 | runOnlyForDeploymentPostprocessing = 0; 122 | }; 123 | 084F520D2640C4EA00B05BFC /* Frameworks */ = { 124 | isa = PBXFrameworksBuildPhase; 125 | buildActionMask = 2147483647; 126 | files = ( 127 | ); 128 | runOnlyForDeploymentPostprocessing = 0; 129 | }; 130 | /* End PBXFrameworksBuildPhase section */ 131 | 132 | /* Begin PBXGroup section */ 133 | 084F51F02640C4C400B05BFC = { 134 | isa = PBXGroup; 135 | children = ( 136 | 084F51FB2640C4C400B05BFC /* BandwidthLimiter */, 137 | 084F52112640C4EA00B05BFC /* com.kishikawakatsumi.BandwidthLimiter.helper */, 138 | 084F52422641813B00B05BFC /* Shared */, 139 | 084F51FA2640C4C400B05BFC /* Products */, 140 | ); 141 | sourceTree = ""; 142 | }; 143 | 084F51FA2640C4C400B05BFC /* Products */ = { 144 | isa = PBXGroup; 145 | children = ( 146 | 084F51F92640C4C400B05BFC /* Bandwidth Limiter.app */, 147 | 084F52102640C4EA00B05BFC /* com.kishikawakatsumi.BandwidthLimiter.helper */, 148 | ); 149 | name = Products; 150 | sourceTree = ""; 151 | }; 152 | 084F51FB2640C4C400B05BFC /* BandwidthLimiter */ = { 153 | isa = PBXGroup; 154 | children = ( 155 | 084F51FC2640C4C400B05BFC /* AppDelegate.swift */, 156 | 084F52362641468F00B05BFC /* SettingsViewController.swift */, 157 | 0872DEBD264253F200E651E2 /* ManageProfilesViewController.swift */, 158 | 0872DEBF264272D200E651E2 /* NewProfileNameViewController.swift */, 159 | 084F52342641466F00B05BFC /* SettingsWindow.storyboard */, 160 | 084F52552641DDB100B05BFC /* TextFieldCell.swift */, 161 | 084F524B26419BF000B05BFC /* App.swift */, 162 | 084F524926419A7700B05BFC /* AppState.swift */, 163 | 084F523C2641685C00B05BFC /* Setting.swift */, 164 | 084F523A26415FA100B05BFC /* Profile.swift */, 165 | 084F522B2640C87900B05BFC /* ExecutionService.swift */, 166 | 084F52402641810D00B05BFC /* ExecutionServiceProxy.swift */, 167 | 084F52512641B6CF00B05BFC /* TrafficShaper.swift */, 168 | 084F52532641BCF400B05BFC /* TemporaryFile.swift */, 169 | 084F52022640C4C400B05BFC /* Main.storyboard */, 170 | 084F52002640C4C400B05BFC /* Assets.xcassets */, 171 | 084F52062640C4C400B05BFC /* BandwidthLimiter.entitlements */, 172 | 084F52052640C4C400B05BFC /* Info.plist */, 173 | ); 174 | path = BandwidthLimiter; 175 | sourceTree = ""; 176 | }; 177 | 084F52112640C4EA00B05BFC /* com.kishikawakatsumi.BandwidthLimiter.helper */ = { 178 | isa = PBXGroup; 179 | children = ( 180 | 084F521D2640C70400B05BFC /* Helper.swift */, 181 | 084F52212640C77400B05BFC /* HelperExecutionService.swift */, 182 | 084F52292640C83600B05BFC /* ConnectionIdentityService.swift */, 183 | 085B23B82642967300C91993 /* OSStatus+Extensions.swift */, 184 | 085B23B52642963F00C91993 /* AuditTokenHack.h */, 185 | 085B23B62642963F00C91993 /* AuditTokenHack.m */, 186 | 084F52122640C4EA00B05BFC /* main.swift */, 187 | 084F521B2640C57600B05BFC /* Info.plist */, 188 | 084F521C2640C60600B05BFC /* launchd.plist */, 189 | 085B23B42642963E00C91993 /* com.kishikawakatsumi.BandwidthLimiter.helper-Bridging-Header.h */, 190 | ); 191 | path = com.kishikawakatsumi.BandwidthLimiter.helper; 192 | sourceTree = ""; 193 | }; 194 | 084F52422641813B00B05BFC /* Shared */ = { 195 | isa = PBXGroup; 196 | children = ( 197 | 084F52432641819400B05BFC /* RemoteApplicationProtocol.swift */, 198 | 084F52252640C7D200B05BFC /* HelperProtocol.swift */, 199 | 084F521F2640C73800B05BFC /* HelperConstants.swift */, 200 | 084F5246264183EE00B05BFC /* Errors.swift */, 201 | 084F52272640C80A00B05BFC /* Result+Extensions.swift */, 202 | ); 203 | path = Shared; 204 | sourceTree = ""; 205 | }; 206 | /* End PBXGroup section */ 207 | 208 | /* Begin PBXNativeTarget section */ 209 | 084F51F82640C4C400B05BFC /* BandwidthLimiter */ = { 210 | isa = PBXNativeTarget; 211 | buildConfigurationList = 084F52092640C4C400B05BFC /* Build configuration list for PBXNativeTarget "BandwidthLimiter" */; 212 | buildPhases = ( 213 | 084F51F52640C4C400B05BFC /* Sources */, 214 | 084F51F62640C4C400B05BFC /* Frameworks */, 215 | 084F51F72640C4C400B05BFC /* Resources */, 216 | 084F52192640C52000B05BFC /* CopyFiles */, 217 | ); 218 | buildRules = ( 219 | ); 220 | dependencies = ( 221 | 084F52182640C50100B05BFC /* PBXTargetDependency */, 222 | ); 223 | name = BandwidthLimiter; 224 | productName = BandwidthLimiter; 225 | productReference = 084F51F92640C4C400B05BFC /* Bandwidth Limiter.app */; 226 | productType = "com.apple.product-type.application"; 227 | }; 228 | 084F520F2640C4EA00B05BFC /* com.kishikawakatsumi.BandwidthLimiter.helper */ = { 229 | isa = PBXNativeTarget; 230 | buildConfigurationList = 084F52142640C4EA00B05BFC /* Build configuration list for PBXNativeTarget "com.kishikawakatsumi.BandwidthLimiter.helper" */; 231 | buildPhases = ( 232 | 084F520C2640C4EA00B05BFC /* Sources */, 233 | 084F520D2640C4EA00B05BFC /* Frameworks */, 234 | 084F520E2640C4EA00B05BFC /* CopyFiles */, 235 | ); 236 | buildRules = ( 237 | ); 238 | dependencies = ( 239 | ); 240 | name = com.kishikawakatsumi.BandwidthLimiter.helper; 241 | productName = com.kishikawakatsumi.BandwidthLimiter.helper; 242 | productReference = 084F52102640C4EA00B05BFC /* com.kishikawakatsumi.BandwidthLimiter.helper */; 243 | productType = "com.apple.product-type.tool"; 244 | }; 245 | /* End PBXNativeTarget section */ 246 | 247 | /* Begin PBXProject section */ 248 | 084F51F12640C4C400B05BFC /* Project object */ = { 249 | isa = PBXProject; 250 | attributes = { 251 | LastSwiftUpdateCheck = 1250; 252 | LastUpgradeCheck = 1250; 253 | TargetAttributes = { 254 | 084F51F82640C4C400B05BFC = { 255 | CreatedOnToolsVersion = 12.5; 256 | }; 257 | 084F520F2640C4EA00B05BFC = { 258 | CreatedOnToolsVersion = 12.5; 259 | LastSwiftMigration = 1250; 260 | }; 261 | }; 262 | }; 263 | buildConfigurationList = 084F51F42640C4C400B05BFC /* Build configuration list for PBXProject "BandwidthLimiter" */; 264 | compatibilityVersion = "Xcode 9.3"; 265 | developmentRegion = en; 266 | hasScannedForEncodings = 0; 267 | knownRegions = ( 268 | en, 269 | Base, 270 | ); 271 | mainGroup = 084F51F02640C4C400B05BFC; 272 | productRefGroup = 084F51FA2640C4C400B05BFC /* Products */; 273 | projectDirPath = ""; 274 | projectRoot = ""; 275 | targets = ( 276 | 084F51F82640C4C400B05BFC /* BandwidthLimiter */, 277 | 084F520F2640C4EA00B05BFC /* com.kishikawakatsumi.BandwidthLimiter.helper */, 278 | ); 279 | }; 280 | /* End PBXProject section */ 281 | 282 | /* Begin PBXResourcesBuildPhase section */ 283 | 084F51F72640C4C400B05BFC /* Resources */ = { 284 | isa = PBXResourcesBuildPhase; 285 | buildActionMask = 2147483647; 286 | files = ( 287 | 084F52352641466F00B05BFC /* SettingsWindow.storyboard in Resources */, 288 | 084F52012640C4C400B05BFC /* Assets.xcassets in Resources */, 289 | 084F52042640C4C400B05BFC /* Main.storyboard in Resources */, 290 | ); 291 | runOnlyForDeploymentPostprocessing = 0; 292 | }; 293 | /* End PBXResourcesBuildPhase section */ 294 | 295 | /* Begin PBXSourcesBuildPhase section */ 296 | 084F51F52640C4C400B05BFC /* Sources */ = { 297 | isa = PBXSourcesBuildPhase; 298 | buildActionMask = 2147483647; 299 | files = ( 300 | 084F524C26419BF000B05BFC /* App.swift in Sources */, 301 | 0872DEBE264253F200E651E2 /* ManageProfilesViewController.swift in Sources */, 302 | 084F52542641BCF400B05BFC /* TemporaryFile.swift in Sources */, 303 | 084F52412641810D00B05BFC /* ExecutionServiceProxy.swift in Sources */, 304 | 084F52522641B6CF00B05BFC /* TrafficShaper.swift in Sources */, 305 | 084F52312640C8C000B05BFC /* Result+Extensions.swift in Sources */, 306 | 0872DEC0264272D200E651E2 /* NewProfileNameViewController.swift in Sources */, 307 | 084F523B26415FA100B05BFC /* Profile.swift in Sources */, 308 | 084F524A26419A7700B05BFC /* AppState.swift in Sources */, 309 | 084F52562641DDB100B05BFC /* TextFieldCell.swift in Sources */, 310 | 084F52372641468F00B05BFC /* SettingsViewController.swift in Sources */, 311 | 084F523D2641685C00B05BFC /* Setting.swift in Sources */, 312 | 084F51FD2640C4C400B05BFC /* AppDelegate.swift in Sources */, 313 | 084F5247264183EE00B05BFC /* Errors.swift in Sources */, 314 | 084F52442641819400B05BFC /* RemoteApplicationProtocol.swift in Sources */, 315 | 084F52332640C8CE00B05BFC /* HelperConstants.swift in Sources */, 316 | 084F52322640C8C000B05BFC /* HelperProtocol.swift in Sources */, 317 | 084F522C2640C87900B05BFC /* ExecutionService.swift in Sources */, 318 | ); 319 | runOnlyForDeploymentPostprocessing = 0; 320 | }; 321 | 084F520C2640C4EA00B05BFC /* Sources */ = { 322 | isa = PBXSourcesBuildPhase; 323 | buildActionMask = 2147483647; 324 | files = ( 325 | 084F52222640C77400B05BFC /* HelperExecutionService.swift in Sources */, 326 | 084F52132640C4EA00B05BFC /* main.swift in Sources */, 327 | 085B23B72642963F00C91993 /* AuditTokenHack.m in Sources */, 328 | 084F52202640C73800B05BFC /* HelperConstants.swift in Sources */, 329 | 084F52282640C80A00B05BFC /* Result+Extensions.swift in Sources */, 330 | 085B23B92642967300C91993 /* OSStatus+Extensions.swift in Sources */, 331 | 084F52452641832D00B05BFC /* RemoteApplicationProtocol.swift in Sources */, 332 | 084F52482641898400B05BFC /* Errors.swift in Sources */, 333 | 084F522A2640C83600B05BFC /* ConnectionIdentityService.swift in Sources */, 334 | 084F521E2640C70400B05BFC /* Helper.swift in Sources */, 335 | 084F52262640C7D200B05BFC /* HelperProtocol.swift in Sources */, 336 | ); 337 | runOnlyForDeploymentPostprocessing = 0; 338 | }; 339 | /* End PBXSourcesBuildPhase section */ 340 | 341 | /* Begin PBXTargetDependency section */ 342 | 084F52182640C50100B05BFC /* PBXTargetDependency */ = { 343 | isa = PBXTargetDependency; 344 | target = 084F520F2640C4EA00B05BFC /* com.kishikawakatsumi.BandwidthLimiter.helper */; 345 | targetProxy = 084F52172640C50100B05BFC /* PBXContainerItemProxy */; 346 | }; 347 | /* End PBXTargetDependency section */ 348 | 349 | /* Begin PBXVariantGroup section */ 350 | 084F52022640C4C400B05BFC /* Main.storyboard */ = { 351 | isa = PBXVariantGroup; 352 | children = ( 353 | 084F52032640C4C400B05BFC /* Base */, 354 | ); 355 | name = Main.storyboard; 356 | sourceTree = ""; 357 | }; 358 | /* End PBXVariantGroup section */ 359 | 360 | /* Begin XCBuildConfiguration section */ 361 | 084F52072640C4C400B05BFC /* Debug */ = { 362 | isa = XCBuildConfiguration; 363 | buildSettings = { 364 | ALWAYS_SEARCH_USER_PATHS = NO; 365 | CLANG_ANALYZER_NONNULL = YES; 366 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 367 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 368 | CLANG_CXX_LIBRARY = "libc++"; 369 | CLANG_ENABLE_MODULES = YES; 370 | CLANG_ENABLE_OBJC_ARC = YES; 371 | CLANG_ENABLE_OBJC_WEAK = YES; 372 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 373 | CLANG_WARN_BOOL_CONVERSION = YES; 374 | CLANG_WARN_COMMA = YES; 375 | CLANG_WARN_CONSTANT_CONVERSION = YES; 376 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 377 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 378 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 379 | CLANG_WARN_EMPTY_BODY = YES; 380 | CLANG_WARN_ENUM_CONVERSION = YES; 381 | CLANG_WARN_INFINITE_RECURSION = YES; 382 | CLANG_WARN_INT_CONVERSION = YES; 383 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 384 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 385 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 386 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 387 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 388 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 389 | CLANG_WARN_STRICT_PROTOTYPES = YES; 390 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 391 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 392 | CLANG_WARN_UNREACHABLE_CODE = YES; 393 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 394 | COPY_PHASE_STRIP = NO; 395 | DEBUG_INFORMATION_FORMAT = dwarf; 396 | ENABLE_STRICT_OBJC_MSGSEND = YES; 397 | ENABLE_TESTABILITY = YES; 398 | GCC_C_LANGUAGE_STANDARD = gnu11; 399 | GCC_DYNAMIC_NO_PIC = NO; 400 | GCC_NO_COMMON_BLOCKS = YES; 401 | GCC_OPTIMIZATION_LEVEL = 0; 402 | GCC_PREPROCESSOR_DEFINITIONS = ( 403 | "DEBUG=1", 404 | "$(inherited)", 405 | ); 406 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 407 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 408 | GCC_WARN_UNDECLARED_SELECTOR = YES; 409 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 410 | GCC_WARN_UNUSED_FUNCTION = YES; 411 | GCC_WARN_UNUSED_VARIABLE = YES; 412 | MACOSX_DEPLOYMENT_TARGET = 10.15; 413 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 414 | MTL_FAST_MATH = YES; 415 | ONLY_ACTIVE_ARCH = YES; 416 | SDKROOT = macosx; 417 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 418 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 419 | }; 420 | name = Debug; 421 | }; 422 | 084F52082640C4C400B05BFC /* Release */ = { 423 | isa = XCBuildConfiguration; 424 | buildSettings = { 425 | ALWAYS_SEARCH_USER_PATHS = NO; 426 | CLANG_ANALYZER_NONNULL = YES; 427 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 428 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 429 | CLANG_CXX_LIBRARY = "libc++"; 430 | CLANG_ENABLE_MODULES = YES; 431 | CLANG_ENABLE_OBJC_ARC = YES; 432 | CLANG_ENABLE_OBJC_WEAK = YES; 433 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 434 | CLANG_WARN_BOOL_CONVERSION = YES; 435 | CLANG_WARN_COMMA = YES; 436 | CLANG_WARN_CONSTANT_CONVERSION = YES; 437 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 438 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 439 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 440 | CLANG_WARN_EMPTY_BODY = YES; 441 | CLANG_WARN_ENUM_CONVERSION = YES; 442 | CLANG_WARN_INFINITE_RECURSION = YES; 443 | CLANG_WARN_INT_CONVERSION = YES; 444 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 445 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 446 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 447 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 448 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 449 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 450 | CLANG_WARN_STRICT_PROTOTYPES = YES; 451 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 452 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 453 | CLANG_WARN_UNREACHABLE_CODE = YES; 454 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 455 | COPY_PHASE_STRIP = NO; 456 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 457 | ENABLE_NS_ASSERTIONS = NO; 458 | ENABLE_STRICT_OBJC_MSGSEND = YES; 459 | GCC_C_LANGUAGE_STANDARD = gnu11; 460 | GCC_NO_COMMON_BLOCKS = YES; 461 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 462 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 463 | GCC_WARN_UNDECLARED_SELECTOR = YES; 464 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 465 | GCC_WARN_UNUSED_FUNCTION = YES; 466 | GCC_WARN_UNUSED_VARIABLE = YES; 467 | MACOSX_DEPLOYMENT_TARGET = 10.15; 468 | MTL_ENABLE_DEBUG_INFO = NO; 469 | MTL_FAST_MATH = YES; 470 | SDKROOT = macosx; 471 | SWIFT_COMPILATION_MODE = wholemodule; 472 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 473 | }; 474 | name = Release; 475 | }; 476 | 084F520A2640C4C400B05BFC /* Debug */ = { 477 | isa = XCBuildConfiguration; 478 | buildSettings = { 479 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 480 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 481 | CODE_SIGN_ENTITLEMENTS = BandwidthLimiter/BandwidthLimiter.entitlements; 482 | CODE_SIGN_IDENTITY = "Developer ID Application"; 483 | CODE_SIGN_STYLE = Manual; 484 | COMBINE_HIDPI_IMAGES = YES; 485 | DEVELOPMENT_TEAM = 27AEDK3C9F; 486 | ENABLE_HARDENED_RUNTIME = YES; 487 | INFOPLIST_FILE = BandwidthLimiter/Info.plist; 488 | LD_RUNPATH_SEARCH_PATHS = ( 489 | "$(inherited)", 490 | "@executable_path/../Frameworks", 491 | ); 492 | PRODUCT_BUNDLE_IDENTIFIER = com.kishikawakatsumi.BandwidthLimiter; 493 | PRODUCT_MODULE_NAME = BandwidthLimiter; 494 | PRODUCT_NAME = "Bandwidth Limiter"; 495 | PROVISIONING_PROFILE_SPECIFIER = ""; 496 | SWIFT_VERSION = 5.0; 497 | }; 498 | name = Debug; 499 | }; 500 | 084F520B2640C4C400B05BFC /* Release */ = { 501 | isa = XCBuildConfiguration; 502 | buildSettings = { 503 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 504 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 505 | CODE_SIGN_ENTITLEMENTS = BandwidthLimiter/BandwidthLimiter.entitlements; 506 | CODE_SIGN_IDENTITY = "Developer ID Application"; 507 | CODE_SIGN_STYLE = Manual; 508 | COMBINE_HIDPI_IMAGES = YES; 509 | DEVELOPMENT_TEAM = 27AEDK3C9F; 510 | ENABLE_HARDENED_RUNTIME = YES; 511 | INFOPLIST_FILE = BandwidthLimiter/Info.plist; 512 | LD_RUNPATH_SEARCH_PATHS = ( 513 | "$(inherited)", 514 | "@executable_path/../Frameworks", 515 | ); 516 | PRODUCT_BUNDLE_IDENTIFIER = com.kishikawakatsumi.BandwidthLimiter; 517 | PRODUCT_MODULE_NAME = BandwidthLimiter; 518 | PRODUCT_NAME = "Bandwidth Limiter"; 519 | PROVISIONING_PROFILE_SPECIFIER = ""; 520 | SWIFT_VERSION = 5.0; 521 | }; 522 | name = Release; 523 | }; 524 | 084F52152640C4EA00B05BFC /* Debug */ = { 525 | isa = XCBuildConfiguration; 526 | buildSettings = { 527 | CLANG_ENABLE_MODULES = YES; 528 | CODE_SIGN_IDENTITY = "Developer ID Application"; 529 | CODE_SIGN_STYLE = Manual; 530 | CREATE_INFOPLIST_SECTION_IN_BINARY = YES; 531 | DEVELOPMENT_TEAM = 27AEDK3C9F; 532 | ENABLE_HARDENED_RUNTIME = YES; 533 | INFOPLIST_FILE = "$(SRCROOT)/$(TARGET_NAME)/Info.plist"; 534 | LD_RUNPATH_SEARCH_PATHS = ( 535 | "$(inherited)", 536 | "@executable_path/../Frameworks", 537 | "@loader_path/../Frameworks", 538 | ); 539 | OTHER_LDFLAGS = ( 540 | "-sectcreate", 541 | __TEXT, 542 | __launchd_plist, 543 | "$(SRCROOT)/${TARGET_NAME}/launchd.plist", 544 | ); 545 | PRODUCT_BUNDLE_IDENTIFIER = "$(TARGET_NAME)"; 546 | PRODUCT_NAME = "$(TARGET_NAME)"; 547 | PROVISIONING_PROFILE_SPECIFIER = ""; 548 | SKIP_INSTALL = YES; 549 | SWIFT_OBJC_BRIDGING_HEADER = "com.kishikawakatsumi.BandwidthLimiter.helper/com.kishikawakatsumi.BandwidthLimiter.helper-Bridging-Header.h"; 550 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 551 | SWIFT_VERSION = 5.0; 552 | }; 553 | name = Debug; 554 | }; 555 | 084F52162640C4EA00B05BFC /* Release */ = { 556 | isa = XCBuildConfiguration; 557 | buildSettings = { 558 | CLANG_ENABLE_MODULES = YES; 559 | CODE_SIGN_IDENTITY = "Developer ID Application"; 560 | CODE_SIGN_STYLE = Manual; 561 | CREATE_INFOPLIST_SECTION_IN_BINARY = YES; 562 | DEVELOPMENT_TEAM = 27AEDK3C9F; 563 | ENABLE_HARDENED_RUNTIME = YES; 564 | INFOPLIST_FILE = "$(SRCROOT)/$(TARGET_NAME)/Info.plist"; 565 | LD_RUNPATH_SEARCH_PATHS = ( 566 | "$(inherited)", 567 | "@executable_path/../Frameworks", 568 | "@loader_path/../Frameworks", 569 | ); 570 | OTHER_LDFLAGS = ( 571 | "-sectcreate", 572 | __TEXT, 573 | __launchd_plist, 574 | "$(SRCROOT)/${TARGET_NAME}/launchd.plist", 575 | ); 576 | PRODUCT_BUNDLE_IDENTIFIER = "$(TARGET_NAME)"; 577 | PRODUCT_NAME = "$(TARGET_NAME)"; 578 | PROVISIONING_PROFILE_SPECIFIER = ""; 579 | SKIP_INSTALL = YES; 580 | SWIFT_OBJC_BRIDGING_HEADER = "com.kishikawakatsumi.BandwidthLimiter.helper/com.kishikawakatsumi.BandwidthLimiter.helper-Bridging-Header.h"; 581 | SWIFT_VERSION = 5.0; 582 | }; 583 | name = Release; 584 | }; 585 | /* End XCBuildConfiguration section */ 586 | 587 | /* Begin XCConfigurationList section */ 588 | 084F51F42640C4C400B05BFC /* Build configuration list for PBXProject "BandwidthLimiter" */ = { 589 | isa = XCConfigurationList; 590 | buildConfigurations = ( 591 | 084F52072640C4C400B05BFC /* Debug */, 592 | 084F52082640C4C400B05BFC /* Release */, 593 | ); 594 | defaultConfigurationIsVisible = 0; 595 | defaultConfigurationName = Release; 596 | }; 597 | 084F52092640C4C400B05BFC /* Build configuration list for PBXNativeTarget "BandwidthLimiter" */ = { 598 | isa = XCConfigurationList; 599 | buildConfigurations = ( 600 | 084F520A2640C4C400B05BFC /* Debug */, 601 | 084F520B2640C4C400B05BFC /* Release */, 602 | ); 603 | defaultConfigurationIsVisible = 0; 604 | defaultConfigurationName = Release; 605 | }; 606 | 084F52142640C4EA00B05BFC /* Build configuration list for PBXNativeTarget "com.kishikawakatsumi.BandwidthLimiter.helper" */ = { 607 | isa = XCConfigurationList; 608 | buildConfigurations = ( 609 | 084F52152640C4EA00B05BFC /* Debug */, 610 | 084F52162640C4EA00B05BFC /* Release */, 611 | ); 612 | defaultConfigurationIsVisible = 0; 613 | defaultConfigurationName = Release; 614 | }; 615 | /* End XCConfigurationList section */ 616 | }; 617 | rootObject = 084F51F12640C4C400B05BFC /* Project object */; 618 | } 619 | -------------------------------------------------------------------------------- /BandwidthLimiter/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | Default 529 | 530 | 531 | 532 | 533 | 534 | 535 | Left to Right 536 | 537 | 538 | 539 | 540 | 541 | 542 | Right to Left 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | Default 554 | 555 | 556 | 557 | 558 | 559 | 560 | Left to Right 561 | 562 | 563 | 564 | 565 | 566 | 567 | Right to Left 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | 668 | 669 | 670 | 671 | 672 | 673 | 674 | 675 | 676 | 677 | 678 | 679 | 680 | 681 | 682 | 683 | 684 | --------------------------------------------------------------------------------