├── 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 |
6 |
7 | In addition, you can set a different speed limit for each host.
8 |
9 |
10 |
11 |
12 | If you leave the hostname blank, all network access will be restricted.
13 |
14 |
15 |
16 |
17 | **Status Icon**
18 |
19 | No bandwidth limitation
20 |
21 |
22 |
23 | Bandwidth limiting enabled
24 |
25 |
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 |
672 |
673 |
674 |
675 |
676 |
677 |
678 |
679 |
680 |
681 |
682 |
683 |
684 |
--------------------------------------------------------------------------------