├── fastlane
├── Appfile
├── Pluginfile
├── Fastfile
└── README.md
├── AppIcon.icns
├── assets
├── m1.png
├── m2.png
├── m3.png
├── r1.png
├── r2.png
└── r3.png
├── sign_update
├── HostsX
├── Assets.xcassets
│ ├── Contents.json
│ ├── add.imageset
│ │ ├── add.png
│ │ └── Contents.json
│ ├── link.imageset
│ │ ├── link@2x.png
│ │ └── Contents.json
│ ├── trash.imageset
│ │ ├── trash@2x.png
│ │ └── Contents.json
│ ├── menuBarIcon.imageset
│ │ ├── rocket.png
│ │ └── Contents.json
│ ├── AppIcon.appiconset
│ │ ├── icon_128x128.png
│ │ ├── icon_16x16.png
│ │ ├── icon_256x256.png
│ │ ├── icon_32x32.png
│ │ ├── icon_512x512.png
│ │ ├── icon_16x16@2x.png
│ │ ├── icon_32x32@2x.png
│ │ ├── icon_128x128@2x.png
│ │ ├── icon_256x256@2x.png
│ │ ├── icon_512x512@2x.png
│ │ └── Contents.json
│ ├── star.imageset
│ │ ├── app.connected.to.app.below.fill@2x.png
│ │ └── Contents.json
│ ├── AccentColor.colorset
│ │ └── Contents.json
│ └── backgroudColor.colorset
│ │ └── Contents.json
├── HostsX.entitlements
├── Component
│ ├── HostsView.swift
│ ├── Dialog
│ │ ├── Dialog.swift
│ │ ├── InfoDialogController.swift
│ │ ├── ConfirmDialogController.swift
│ │ ├── InfoDialogController.xib
│ │ └── ConfirmDialogController.xib
│ └── HostsURLCell.swift
├── Info.plist
├── Model
│ ├── HostsError.swift
│ └── Hosts.swift
├── AppDelegate.swift
├── Module
│ ├── Help
│ │ └── AboutViewController.swift
│ └── Remote
│ │ ├── HostsConfigController.swift
│ │ └── RemoteConfigController.swift
├── Service
│ ├── Network.swift
│ ├── Localization.swift
│ ├── RemoteSource.swift
│ ├── FileHelper.swift
│ └── NotificationHelper.swift
├── zh-Hans.lproj
│ └── Localizable.strings
├── AppMenu.swift
├── en.lproj
│ └── Localizable.strings
├── Extension
│ ├── Foundition+.swift
│ └── AppKit+.swift
├── Constant.swift
├── Menu.xib
├── Help.storyboard
└── Base.lproj
│ └── Remote.storyboard
├── Gemfile
├── CHANGELOG_SC.md
├── README.md
├── CHANGELOG.md
├── .github
└── workflows
│ └── main.yml
├── .gitignore
├── Gemfile.lock
├── HostsX.xcodeproj
└── project.pbxproj
└── LICENSE
/fastlane/Appfile:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/AppIcon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HostsTools/macOS/HEAD/AppIcon.icns
--------------------------------------------------------------------------------
/assets/m1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HostsTools/macOS/HEAD/assets/m1.png
--------------------------------------------------------------------------------
/assets/m2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HostsTools/macOS/HEAD/assets/m2.png
--------------------------------------------------------------------------------
/assets/m3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HostsTools/macOS/HEAD/assets/m3.png
--------------------------------------------------------------------------------
/assets/r1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HostsTools/macOS/HEAD/assets/r1.png
--------------------------------------------------------------------------------
/assets/r2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HostsTools/macOS/HEAD/assets/r2.png
--------------------------------------------------------------------------------
/assets/r3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HostsTools/macOS/HEAD/assets/r3.png
--------------------------------------------------------------------------------
/sign_update:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HostsTools/macOS/HEAD/sign_update
--------------------------------------------------------------------------------
/HostsX/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/HostsX/Assets.xcassets/add.imageset/add.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HostsTools/macOS/HEAD/HostsX/Assets.xcassets/add.imageset/add.png
--------------------------------------------------------------------------------
/HostsX/Assets.xcassets/link.imageset/link@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HostsTools/macOS/HEAD/HostsX/Assets.xcassets/link.imageset/link@2x.png
--------------------------------------------------------------------------------
/HostsX/Assets.xcassets/trash.imageset/trash@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HostsTools/macOS/HEAD/HostsX/Assets.xcassets/trash.imageset/trash@2x.png
--------------------------------------------------------------------------------
/HostsX/Assets.xcassets/menuBarIcon.imageset/rocket.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HostsTools/macOS/HEAD/HostsX/Assets.xcassets/menuBarIcon.imageset/rocket.png
--------------------------------------------------------------------------------
/HostsX/Assets.xcassets/AppIcon.appiconset/icon_128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HostsTools/macOS/HEAD/HostsX/Assets.xcassets/AppIcon.appiconset/icon_128x128.png
--------------------------------------------------------------------------------
/HostsX/Assets.xcassets/AppIcon.appiconset/icon_16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HostsTools/macOS/HEAD/HostsX/Assets.xcassets/AppIcon.appiconset/icon_16x16.png
--------------------------------------------------------------------------------
/HostsX/Assets.xcassets/AppIcon.appiconset/icon_256x256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HostsTools/macOS/HEAD/HostsX/Assets.xcassets/AppIcon.appiconset/icon_256x256.png
--------------------------------------------------------------------------------
/HostsX/Assets.xcassets/AppIcon.appiconset/icon_32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HostsTools/macOS/HEAD/HostsX/Assets.xcassets/AppIcon.appiconset/icon_32x32.png
--------------------------------------------------------------------------------
/HostsX/Assets.xcassets/AppIcon.appiconset/icon_512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HostsTools/macOS/HEAD/HostsX/Assets.xcassets/AppIcon.appiconset/icon_512x512.png
--------------------------------------------------------------------------------
/HostsX/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HostsTools/macOS/HEAD/HostsX/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png
--------------------------------------------------------------------------------
/HostsX/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HostsTools/macOS/HEAD/HostsX/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png
--------------------------------------------------------------------------------
/HostsX/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HostsTools/macOS/HEAD/HostsX/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png
--------------------------------------------------------------------------------
/HostsX/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HostsTools/macOS/HEAD/HostsX/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png
--------------------------------------------------------------------------------
/HostsX/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HostsTools/macOS/HEAD/HostsX/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png
--------------------------------------------------------------------------------
/fastlane/Pluginfile:
--------------------------------------------------------------------------------
1 | # Autogenerated by fastlane
2 | #
3 | # Ensure this file is checked in to source control!
4 |
5 | gem 'fastlane-plugin-versioning'
6 |
7 | gem 'fastlane-plugin-dmg'
8 |
--------------------------------------------------------------------------------
/HostsX/Assets.xcassets/star.imageset/app.connected.to.app.below.fill@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HostsTools/macOS/HEAD/HostsX/Assets.xcassets/star.imageset/app.connected.to.app.below.fill@2x.png
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 |
3 | gem "fastlane"
4 |
5 | plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile')
6 | eval_gemfile(plugins_path) if File.exist?(plugins_path)
7 |
--------------------------------------------------------------------------------
/HostsX/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "mac"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/HostsX/HostsX.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.files.user-selected.read-only
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/HostsX/Assets.xcassets/add.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "mac",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "add.png",
9 | "idiom" : "mac",
10 | "scale" : "2x"
11 | }
12 | ],
13 | "info" : {
14 | "author" : "xcode",
15 | "version" : 1
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/HostsX/Assets.xcassets/link.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "mac",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "link@2x.png",
9 | "idiom" : "mac",
10 | "scale" : "2x"
11 | }
12 | ],
13 | "info" : {
14 | "author" : "xcode",
15 | "version" : 1
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/HostsX/Assets.xcassets/trash.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "mac",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "trash@2x.png",
9 | "idiom" : "mac",
10 | "scale" : "2x"
11 | }
12 | ],
13 | "info" : {
14 | "author" : "xcode",
15 | "version" : 1
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/HostsX/Assets.xcassets/menuBarIcon.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "mac",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "rocket.png",
9 | "idiom" : "mac",
10 | "scale" : "2x"
11 | }
12 | ],
13 | "info" : {
14 | "author" : "xcode",
15 | "version" : 1
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/fastlane/Fastfile:
--------------------------------------------------------------------------------
1 | default_platform(:mac)
2 |
3 | platform :mac do
4 |
5 |
6 | lane :package do
7 |
8 | build
9 | clean
10 |
11 | end
12 |
13 |
14 | lane :build do
15 | build_app(
16 | clean: true,
17 | silent: true,
18 | export_method: 'mac-application'
19 | )
20 | end
21 |
22 | lane :clean do
23 | sh "rm -vfr ~/Library/Developer/Xcode/Archives/*"
24 | end
25 |
26 |
27 | end
28 |
--------------------------------------------------------------------------------
/HostsX/Assets.xcassets/star.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "mac",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "app.connected.to.app.below.fill@2x.png",
9 | "idiom" : "mac",
10 | "scale" : "2x"
11 | }
12 | ],
13 | "info" : {
14 | "author" : "xcode",
15 | "version" : 1
16 | },
17 | "properties" : {
18 | "template-rendering-intent" : "template"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/HostsX/Component/HostsView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HostsView.swift
3 | // HostsX
4 | //
5 | // Created by zm on 2021/12/23.
6 | //
7 |
8 | import Cocoa
9 |
10 | class HostsView: NSView {
11 |
12 | override func updateLayer() {
13 | if #available(macOS 10.13, *) {
14 | self.layer?.backgroundColor = NSColor.backgroud?.cgColor
15 | } else {
16 | // Fallback on earlier versions
17 | }
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/HostsX/Component/Dialog/Dialog.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Dialog.swift
3 | // HostsX
4 | //
5 | // Created by zm on 2021/12/6.
6 | //
7 |
8 | import Foundation
9 | import AppKit
10 |
11 |
12 | struct Dialog {
13 |
14 | static func showConfirm(from: NSViewController, message: String, completion: @escaping VoidClosure) {
15 | from.presentAsSheet(ConfirmDialogController(message, completion: completion))
16 | }
17 |
18 | static func showInfo(from: NSViewController, message: String) {
19 | from.presentAsSheet(InfoDialogController(message: message))
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/HostsX/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | LSUIElement
6 |
7 | NSAppTransportSecurity
8 |
9 | NSAllowsArbitraryLoads
10 |
11 |
12 | SUEnableAutomaticChecks
13 |
14 | SUFeedURL
15 | https://zzzm.github.io/HostsX/appcast.xml
16 | SUPublicEDKey
17 | IqmAL/4PBUY1LejXocSszpdeqxAMQXLi3ydovPOOvvs=
18 |
19 |
20 |
--------------------------------------------------------------------------------
/HostsX/Assets.xcassets/backgroudColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "platform" : "osx",
6 | "reference" : "textBackgroundColor"
7 | },
8 | "idiom" : "mac"
9 | },
10 | {
11 | "appearances" : [
12 | {
13 | "appearance" : "luminosity",
14 | "value" : "dark"
15 | }
16 | ],
17 | "color" : {
18 | "platform" : "osx",
19 | "reference" : "windowBackgroundColor"
20 | },
21 | "idiom" : "mac"
22 | }
23 | ],
24 | "info" : {
25 | "author" : "xcode",
26 | "version" : 1
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/HostsX/Model/HostsError.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HostsError.swift
3 | // HostsX
4 | //
5 | // Created by zm on 2021/12/20.
6 | //
7 |
8 | import Foundation
9 |
10 | enum HostsError: Error, LocalizedError {
11 | case invalidHosts, invalidURL, compile, execute(String), cancelled
12 |
13 | var errorDescription: String? {
14 | switch self {
15 | case .invalidHosts: return Localization.Error.invalidHosts
16 | case .invalidURL: return Localization.Error.invalidURL
17 | case .compile: return Localization.Error.compile
18 | case .execute(let message): return "\(Localization.Error.execute) \(message)"
19 | case .cancelled: return Localization.Error.cancelled
20 | }
21 | }
22 | }
23 |
24 |
25 |
--------------------------------------------------------------------------------
/HostsX/Component/HostsURLCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HostsURLCell.swift
3 | // HostsX
4 | //
5 | // Created by zm on 2021/12/2.
6 | //
7 |
8 | import Cocoa
9 |
10 | class HostsURLCell: NSTableCellView {
11 |
12 | @IBOutlet weak var titleLabel: NSTextField!
13 |
14 | @IBOutlet weak var starLabel: NSTextField!
15 |
16 | @IBOutlet weak var urlLabel: NSTextField!
17 |
18 |
19 | override func awakeFromNib() {
20 | starLabel.stringValue = Localization.Hosts.origin
21 | }
22 |
23 | var hosts: Hosts! {
24 | didSet {
25 | titleLabel.stringValue = hosts.alias
26 | starLabel.isHidden = !hosts.isOrigin
27 | urlLabel.stringValue = hosts.url
28 | }
29 | }
30 |
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/HostsX/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // HostsX
4 | //
5 | // Created by zm on 2021/12/2.
6 | //
7 |
8 | import Cocoa
9 |
10 |
11 | @main
12 | class AppDelegate: NSObject, NSApplicationDelegate {
13 |
14 | private lazy var statusItem = NSStatusItem.system
15 |
16 |
17 | @IBOutlet weak var menu: AppMenu!
18 |
19 |
20 |
21 | func applicationDidFinishLaunching(_ aNotification: Notification) {
22 |
23 | statusItem.setMenuBarIcon()
24 | statusItem.menu = menu
25 |
26 | }
27 |
28 |
29 | func applicationWillTerminate(_ aNotification: Notification) {
30 | // Insert code here to tear down your application
31 | }
32 |
33 | func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
34 | return true
35 | }
36 |
37 |
38 | }
39 |
40 |
--------------------------------------------------------------------------------
/HostsX/Component/Dialog/InfoDialogController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // InfoDialogController.swift
3 | // HostsX
4 | //
5 | // Created by zm on 2021/12/6.
6 | //
7 |
8 | import Cocoa
9 |
10 | class InfoDialogController: NSViewController {
11 |
12 | let message: String
13 |
14 | @IBOutlet weak var messageLabel: NSTextFieldCell!
15 |
16 | @IBOutlet weak var doneButton: NSButton!
17 |
18 | init(message: String) {
19 | self.message = message
20 | super.init(nibName: nil, bundle: nil)
21 | preferredContentSize = .dialog
22 | }
23 |
24 | required init?(coder: NSCoder) {
25 | fatalError("init(coder:) has not been implemented")
26 | }
27 |
28 | override func viewDidLoad() {
29 | super.viewDidLoad()
30 | messageLabel.stringValue = message
31 | doneButton.title = Localization.Dialog.done
32 | }
33 |
34 | @IBAction func onDone(_ sender: Any) {
35 | dismiss(.none)
36 | }
37 |
38 | }
39 |
40 |
--------------------------------------------------------------------------------
/HostsX/Model/Hosts.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Hosts.swift
3 | // HostsX
4 | //
5 | // Created by zm on 2021/12/3.
6 | //
7 |
8 | import Foundation
9 | import AppKit
10 |
11 | enum HostsStatus: Codable {
12 |
13 | case unknown, available, unavailable
14 |
15 | var icon: NSImage {
16 | switch self {
17 | case .unknown: return .init(named: NSImage.statusNoneName)!
18 | case .available: return .init(named: NSImage.statusAvailableName)!
19 | case .unavailable: return .init(named: NSImage.statusUnavailableName)!
20 | }
21 | }
22 |
23 | }
24 |
25 |
26 | class Hosts: Codable {
27 |
28 | let alias: String, url: String
29 | var isOrigin: Bool
30 | var status: HostsStatus = .unknown
31 |
32 | var isAvailable: Bool {
33 | .available == status
34 | }
35 |
36 | init(_ alias: String, url: String, isOrigin: Bool = false) {
37 | self.alias = alias
38 | self.url = url
39 | self.isOrigin = isOrigin
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/fastlane/README.md:
--------------------------------------------------------------------------------
1 | fastlane documentation
2 | ----
3 |
4 | # Installation
5 |
6 | Make sure you have the latest version of the Xcode command line tools installed:
7 |
8 | ```sh
9 | xcode-select --install
10 | ```
11 |
12 | For _fastlane_ installation instructions, see [Installing _fastlane_](https://docs.fastlane.tools/#installing-fastlane)
13 |
14 | # Available Actions
15 |
16 | ## Mac
17 |
18 | ### mac package
19 |
20 | ```sh
21 | [bundle exec] fastlane mac package
22 | ```
23 |
24 |
25 |
26 | ### mac build
27 |
28 | ```sh
29 | [bundle exec] fastlane mac build
30 | ```
31 |
32 |
33 |
34 | ### mac clean
35 |
36 | ```sh
37 | [bundle exec] fastlane mac clean
38 | ```
39 |
40 |
41 |
42 | ----
43 |
44 | This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run.
45 |
46 | More information about _fastlane_ can be found on [fastlane.tools](https://fastlane.tools).
47 |
48 | The documentation of _fastlane_ can be found on [docs.fastlane.tools](https://docs.fastlane.tools).
49 |
--------------------------------------------------------------------------------
/CHANGELOG_SC.md:
--------------------------------------------------------------------------------
1 | ## 2.8.1 - 2022-04-06
2 |
3 | ### Changed
4 | - 更新应用图标
5 | - 更新应用字体
6 | - 更新菜单栏图标
7 | - 用图标显示 hosts 状态
8 |
9 | ---
10 |
11 | ## 2.8.0 - 2022-01-14
12 |
13 | ### Changed
14 | - 应用名称由 **HostsToolForMac** 更新为 **HostsX**
15 | - 更新应用图标
16 | - 更新菜单栏菜单显示条目
17 |
18 | ### Added
19 | - 新增关于界面
20 | - 新增远程配置界面
21 |
22 | ---
23 | ## 2.7.0 - 2020-11-17
24 |
25 | ### Changed
26 | - 更新界面
27 |
28 | ### Security
29 | - 支持 macOS Big Sur 11.0.1
30 |
31 | ---
32 |
33 | ## 2.6.1 - 2020-07-30
34 |
35 | ### Fixed
36 | - 修复 bug
37 |
38 | ---
39 |
40 | ## 2.6.0 - 2020-07-06
41 |
42 | ### Changed
43 | - 支持在线更新
44 | - 更新菜单栏菜单显示条目
45 | - 更新设置界面
46 |
47 | ---
48 |
49 | ## 2.5.0 - 2020-04-15
50 |
51 | ### Changed
52 | - 更新设置界面
53 | - 更新 hosts 下载源
54 |
55 | ### Added
56 | - 增加主页菜单条目
57 |
58 | ---
59 |
60 | ## 2.4.0 - 2019-12-31
61 |
62 | ### Changed
63 | - 更新界面
64 |
65 | ### Security
66 | - 支持 macOS Catalina
67 |
68 | ---
69 |
70 | ## 2.3.2 - 2019-04-01
71 |
72 | ### Security
73 | - 升级至 Swift 5
74 |
75 | ---
76 |
77 | ## 注意
78 |
79 | - `Added` 新添加的功能
80 | - `Changed` 对现有功能的变更
81 | - `Deprecated` 已经不建议使用,准备很快移除的功能
82 | - `Removed` 已经移除的功能
83 | - `Fixed` 对bug的修复
84 | - `Security` 对安全的改进
--------------------------------------------------------------------------------
/HostsX/Module/Help/AboutViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AboutViewController.swift
3 | // HostsX
4 | //
5 | // Created by zm on 2021/12/22.
6 | //
7 |
8 | import Cocoa
9 |
10 | class AboutViewController: NSViewController {
11 |
12 | @IBOutlet weak var iconImageView: NSImageView!
13 | @IBOutlet weak var nameLabel: NSTextField!
14 | @IBOutlet weak var versionLabel: NSTextField!
15 | @IBOutlet weak var copyrightLabel: NSTextField!
16 |
17 | private var name = Bundle.main.bundleName ?? "HostsX"
18 |
19 | private var version: String {
20 | guard let short = Bundle.main.shortVersion, let build = Bundle.main.version else {
21 | return ""
22 | }
23 | return "\(Localization.About.version) \(short) (\(build))"
24 | }
25 | private var copyright = Bundle.main.humanReadableCopyright ?? ""
26 |
27 | override func viewDidLoad() {
28 | super.viewDidLoad()
29 | iconImageView.image = NSApp.applicationIconImage
30 | nameLabel.stringValue = name
31 | versionLabel.stringValue = version
32 | copyrightLabel.stringValue = copyright
33 | }
34 |
35 |
36 | @IBAction func onGitHub(_ sender: Any) {
37 | NSWorkspace.open(HostsUrl.github)
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/HostsX/Component/Dialog/ConfirmDialogController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ConfirmDialogController.swift
3 | // HostsX
4 | //
5 | // Created by zm on 2021/12/5.
6 | //
7 |
8 | import Cocoa
9 |
10 | class ConfirmDialogController: NSViewController {
11 |
12 | let message: String, completion: VoidClosure?
13 |
14 | @IBOutlet weak var cancelButton: NSButton!
15 | @IBOutlet weak var doneButton: NSButton!
16 | @IBOutlet weak var messageLabel: NSTextFieldCell!
17 |
18 | init(_ message: String, completion: VoidClosure?) {
19 |
20 | self.message = message
21 | self.completion = completion
22 | super.init(nibName: nil, bundle: nil)
23 | preferredContentSize = .dialog
24 |
25 | }
26 |
27 | required init?(coder: NSCoder) {
28 | fatalError("init(coder:) has not been implemented")
29 | }
30 |
31 | override func viewDidLoad() {
32 | super.viewDidLoad()
33 | messageLabel.stringValue = message
34 | doneButton.title = Localization.Dialog.done
35 | cancelButton.title = Localization.Dialog.cancel
36 | }
37 |
38 |
39 | @IBAction func onDismiss(_ sender: Any) {
40 | dismiss(.none)
41 | }
42 |
43 | @IBAction func onDone(_ sender: Any) {
44 | completion?()
45 | dismiss(.none)
46 | }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/HostsX/Service/Network.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | // HostsX
4 | //
5 | // Created by zm on 2021/12/2.
6 | //
7 |
8 | import Foundation
9 |
10 | struct Network {
11 |
12 | private static var task: URLSessionDataTask?
13 |
14 | static func check(_ urlString: String, completion: @escaping (HostsStatus) -> Void){
15 |
16 | completion(.unknown)
17 |
18 | cancel()
19 |
20 | guard let url = URL(string: urlString) else {
21 | return completion(.unavailable)
22 | }
23 |
24 |
25 | let request = URLRequest(url: url, cachePolicy: .reloadIgnoringCacheData, timeoutInterval: 30)
26 |
27 | task = URLSession.shared.dataTask(with: request) { data, response, error in
28 | if let error = error {
29 | return completion(error.isCancelled ? .unknown : .unavailable)
30 | }
31 |
32 | guard let response = response as? HTTPURLResponse else {
33 | return completion(.unavailable)
34 | }
35 | guard 200 == response.statusCode else {
36 | return completion(.unavailable)
37 | }
38 | return completion(.available)
39 | }
40 |
41 | task?.resume()
42 |
43 | }
44 |
45 | static func cancel() {
46 | task?.cancel()
47 | }
48 | }
49 |
50 |
51 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | HostsX
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | A lightweight macOS App for updating local hosts
12 |
13 | ## Features
14 | - [x] Localization (简体中文、English)
15 | - [x] Dark mode
16 |
17 | ## Compatibility
18 | - Requires **macOS 10.12** or later
19 |
20 | ## Changelogs
21 | - [简体中文](CHANGELOG_SC.md)
22 | - [English](CHANGELOG.md)
23 |
24 | ## Snapshots
25 | - Menubar
26 |
27 |
28 |
29 | - Remote configuration
30 |
31 |
32 |
33 |
34 | ## Note
35 | - Add DNS entries between `# My Hosts Start` and `# My Hosts End`
36 | ```
37 | # My Hosts Start
38 |
39 | 0.0.0.0 www.example0.com
40 | 1.1.1.1 www.example1.com
41 | 2.2.2.2 www.example2.com
42 |
43 | # My Hosts End
44 | ```
45 |
46 | ## Dependencies
47 | - [Sparkle](https://github.com/sparkle-project/Sparkle)
48 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 2.8.1 - 2022-04-06
2 |
3 | ### Changed
4 | - Updated App icon
5 | - Updated App font
6 | - Updated menubar icon
7 | - Show hosts status with icons
8 |
9 | ---
10 |
11 | ## 2.8.0 - 2022-01-14
12 |
13 | ### Changed
14 | - Updated App name from **HostsToolForMac** to **HostsX**
15 | - Updated App icon
16 | - Updated menu items in the menubar
17 |
18 | ### Added
19 | - Added about view
20 | - Added remote configuration view
21 |
22 | ---
23 |
24 | ## 2.7.0 - 2020-11-17
25 |
26 | ### Changed
27 | - Updated UI
28 |
29 | ### Security
30 | - Supported macOS Big Sur 11.0.1
31 |
32 | ---
33 |
34 | ## 2.6.1 - 2020-07-30
35 |
36 | ### Fixed
37 | - Fixed Bugs
38 |
39 | ---
40 |
41 | ## 2.6.0 - 2020-07-06
42 |
43 | ### Changed
44 | - Support online upgrade
45 | - Updadted menu items in the menubar
46 | - Updated settings view
47 |
48 | ---
49 |
50 | ## 2.5.0 - 2020-04-15
51 |
52 | ### Changed
53 | - Updated settings view
54 | - Updated hosts sources
55 |
56 | ### Added
57 | - Added homepage menuItem
58 |
59 | ---
60 |
61 | ## 2.4.0 - 2019-12-31
62 |
63 | ### Changed
64 | - Updated UI
65 |
66 | ### Security
67 | - macOS Catalina Support
68 |
69 | ---
70 |
71 | ## 2.3.2 - 2019-04-01
72 |
73 | ### Security
74 | - Upgrade to Swift 5
75 |
76 | ---
77 |
78 | ## Note
79 |
80 | - `Added` for new features
81 | - `Changed` for changes in existing functionality
82 | - `Deprecated` for soon-to-be removed features
83 | - `Removed` for now removed features
84 | - `Fixed` for any bug fixes
85 | - `Security` in case of vulnerabilities
86 |
--------------------------------------------------------------------------------
/HostsX/zh-Hans.lproj/Localizable.strings:
--------------------------------------------------------------------------------
1 | /*
2 | File.strings
3 | HostsX
4 |
5 | Created by zm on 2021/12/17.
6 |
7 | */
8 | "menu.local" = "本地";
9 | "menu.remote" = "远程";
10 | "menu.remote.download" = "下载";
11 | "menu.remote.config" = "配置";
12 | "menu.help" = "帮助";
13 | "menu.help.check" = "检查更新";
14 | "menu.help.about" = "关于";
15 | "menu.quit" = "退出";
16 |
17 | "hosts.alias" = "名称";
18 | "hosts.url" = "URL";
19 | "hosts.origin" = "下载源";
20 | "hosts.available" = "可用";
21 | "hosts.unavailable" = "不可用";
22 | "hosts.unknown" = "未知";
23 |
24 | "dialog.cancel" = "取消";
25 | "dialog.done" = "好的";
26 |
27 | "remote.hosts" = "Hosts 配置";
28 | "remote.hosts.ok" = "完成";
29 | "remote.alias.prompt" = "3 ~ 8 字符";
30 | "remote.alias.placeholder" = "输入名称 ...";
31 | "remote.url.placeholder" = "输入URL ...";
32 | "remote.alias.prompt.empty" = "名称为空";
33 | "remote.alias.prompt.invalid" = "无效名称";
34 | "remote.alias.prompt.exists" = "名称已存在";
35 | "remote.url.prompt.empty" = "URL为空";
36 | "remote.url.prompt.invalid" = "无效URL";
37 | "remote.url.prompt.exists" = "URL已存在";
38 |
39 | "remote.hosts.confirm.open" = "确认打开 \n\"%1$@\"?";
40 | "remote.hosts.confirm.remove" = "确认删除 \n\"%1$@\"?";
41 | "remote.hosts.confirm.origin" = "确认设置 \n\"%1$@\"\n 为下载源?";
42 |
43 | "about.version" = "版本";
44 | "update.finished" = "更新完成";
45 | "update.succeeded" = "更新成功";
46 | "update.failed" = "更新失败";
47 | "update.unfinished" = "更新未完成";
48 |
49 | "error.invalidHosts" = "无效 hosts";
50 | "error.invalidURL" = "无效 URL";
51 | "error.compile" = "脚本编译错误";
52 | "error.execute" = "脚本执行错误:";
53 | "error.cancelled" = "用户取消";
54 |
--------------------------------------------------------------------------------
/HostsX/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "icon_16x16.png",
5 | "idiom" : "mac",
6 | "scale" : "1x",
7 | "size" : "16x16"
8 | },
9 | {
10 | "filename" : "icon_16x16@2x.png",
11 | "idiom" : "mac",
12 | "scale" : "2x",
13 | "size" : "16x16"
14 | },
15 | {
16 | "filename" : "icon_32x32.png",
17 | "idiom" : "mac",
18 | "scale" : "1x",
19 | "size" : "32x32"
20 | },
21 | {
22 | "filename" : "icon_32x32@2x.png",
23 | "idiom" : "mac",
24 | "scale" : "2x",
25 | "size" : "32x32"
26 | },
27 | {
28 | "filename" : "icon_128x128.png",
29 | "idiom" : "mac",
30 | "scale" : "1x",
31 | "size" : "128x128"
32 | },
33 | {
34 | "filename" : "icon_128x128@2x.png",
35 | "idiom" : "mac",
36 | "scale" : "2x",
37 | "size" : "128x128"
38 | },
39 | {
40 | "filename" : "icon_256x256.png",
41 | "idiom" : "mac",
42 | "scale" : "1x",
43 | "size" : "256x256"
44 | },
45 | {
46 | "filename" : "icon_256x256@2x.png",
47 | "idiom" : "mac",
48 | "scale" : "2x",
49 | "size" : "256x256"
50 | },
51 | {
52 | "filename" : "icon_512x512.png",
53 | "idiom" : "mac",
54 | "scale" : "1x",
55 | "size" : "512x512"
56 | },
57 | {
58 | "filename" : "icon_512x512@2x.png",
59 | "idiom" : "mac",
60 | "scale" : "2x",
61 | "size" : "512x512"
62 | }
63 | ],
64 | "info" : {
65 | "author" : "xcode",
66 | "version" : 1
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/HostsX/AppMenu.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppMenu.swift
3 | // HostsX
4 | //
5 | // Created by zm on 2021/12/17.
6 | //
7 |
8 | import Cocoa
9 |
10 | class AppMenu: NSMenu {
11 |
12 | @IBOutlet weak var localItem: NSMenuItem!
13 |
14 | @IBOutlet weak var remoteItem: NSMenuItem!
15 | @IBOutlet weak var downloadItem: NSMenuItem!
16 | @IBOutlet weak var configItem: NSMenuItem!
17 |
18 | @IBOutlet weak var helpItem: NSMenuItem!
19 | @IBOutlet weak var checkItem: NSMenuItem!
20 | @IBOutlet weak var aboutItem: NSMenuItem!
21 |
22 | @IBOutlet weak var quitItem: NSMenuItem!
23 |
24 | private var wc: NSWindowController?
25 |
26 | override func awakeFromNib() {
27 | localItem.title = Localization.Menu.local
28 | remoteItem.title = Localization.Menu.remote
29 | downloadItem.title = Localization.Menu.remoteDownload
30 | configItem.title = Localization.Menu.remoteConfig
31 | helpItem.title = Localization.Menu.help
32 | checkItem.title = Localization.Menu.helpCheck
33 | aboutItem.title = Localization.Menu.helpAbout
34 | quitItem.title = Localization.Menu.quit
35 | }
36 |
37 | @IBAction func onLoacl(_ sender: NSMenuItem) {
38 | FileHelper.localUpdate {
39 | NotificationHelper.deliver(category: .loacl, error: $0)
40 | }
41 | }
42 |
43 |
44 | @IBAction func onDownload(_ sender: NSMenuItem) {
45 | sender.isEnabled.toggle()
46 | FileHelper.remoteUpdate {
47 | sender.isEnabled.toggle()
48 | NotificationHelper.deliver(category: .remote, error: $0)
49 | }
50 | }
51 |
52 | @IBAction func onConfigure(_ sender: Any) {
53 | wc.show(.remote)
54 | }
55 |
56 |
57 | @IBAction func onAbout(_ sender: Any) {
58 | wc.show(.help)
59 | }
60 |
61 | }
62 |
--------------------------------------------------------------------------------
/HostsX/en.lproj/Localizable.strings:
--------------------------------------------------------------------------------
1 | /*
2 | File.strings
3 | HostsX
4 |
5 | Created by zm on 2021/12/17.
6 |
7 | */
8 | "menu.local" = "Local";
9 | "menu.remote" = "Remote";
10 | "menu.remote.download" = "Download";
11 | "menu.remote.config" = "Configure";
12 | "menu.help" = "Help";
13 | "menu.help.check" = "Check for updates";
14 | "menu.help.about" = "About";
15 | "menu.quit" = "Quit";
16 |
17 | "hosts.alias" = "Alias";
18 | "hosts.url" = "URL";
19 | "hosts.origin" = "Origin";
20 | "hosts.available" = "Available";
21 | "hosts.unavailable" = "Unavailable";
22 | "hosts.unknown" = "Unknown";
23 |
24 | "dialog.cancel" = "Cancel";
25 | "dialog.done" = "Done";
26 | "remote.hosts" = "Hosts Config";
27 | "remote.hosts.ok" = "OK";
28 | "remote.alias.prompt" = "3 ~ 6 Characters";
29 | "remote.alias.placeholder" = "Enter Alias ...";
30 | "remote.url.placeholder" = "Enter URL ...";
31 |
32 | "remote.alias.prompt.empty" = "Alias is empty";
33 | "remote.alias.prompt.invalid" = "Invalid alias";
34 | "remote.alias.prompt.exists" = "Alias already exists";
35 | "remote.url.prompt.empty" = "URL is empty";
36 | "remote.url.prompt.invalid" = "Invalid URL";
37 | "remote.url.prompt.exists" = "URL already exists";
38 |
39 | "remote.hosts.confirm.open" = "Confirm to open \n\"%1$@\"?";
40 | "remote.hosts.confirm.remove" = "Confirm to remove \n\"%1$@\"?";
41 | "remote.hosts.confirm.origin" = "Confirm to set \n\"%1$@\"\n as origin?";
42 |
43 | "about.version" = "Version";
44 |
45 | "update.finished" = "Update Finished";
46 | "update.succeeded" = " Update Succeeded";
47 | "update.failed" = " Update Failed";
48 | "update.unfinished" = " Update Unfinished";
49 |
50 | "error.invalidHosts" = "Invalid hosts";
51 | "error.invalidURL" = "Invalid URL";
52 | "error.compile" = "Script compile error";
53 | "error.execute" = "Script execute error:";
54 | "error.cancelled" = "User cancelled";
55 |
--------------------------------------------------------------------------------
/HostsX/Extension/Foundition+.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Foundition+.swift
3 | // HostsX
4 | //
5 | // Created by zm on 2021/12/15.
6 | //
7 |
8 | import Foundation
9 |
10 | extension NSObject {
11 | var className: String { String(describing: type(of: self)) }
12 | static var className: String { String(describing: self) }
13 | }
14 |
15 | extension String {
16 | var localized: String { NSLocalizedString(self, comment: "") }
17 |
18 | func localized(_ arg: String) -> String { String(format: localized, arg) }
19 | }
20 |
21 | extension NSAppleScript {
22 |
23 | convenience init?(command: String) {
24 | let source = """
25 | do shell script \
26 | "\(command)" \
27 | with administrator privileges
28 | """
29 | self.init(source: source)
30 | }
31 |
32 | }
33 |
34 | extension Optional where Wrapped == NSAppleScript {
35 | func doShell() throws {
36 | var errorInfo : NSDictionary?
37 | guard let appleScript = self else {
38 | throw HostsError.compile
39 | }
40 | appleScript.executeAndReturnError(&errorInfo)
41 |
42 | guard let errorMessage = errorInfo?["NSAppleScriptErrorMessage"] as? String else {
43 | return
44 | }
45 |
46 | switch errorInfo?["NSAppleScriptErrorNumber"] as? Int {
47 | case -128: throw HostsError.cancelled
48 | default: throw HostsError.execute(errorMessage)
49 | }
50 |
51 | }
52 | }
53 |
54 | extension Error {
55 |
56 | var code: Int {
57 | (self as NSError).code
58 | }
59 |
60 | var isCancelled: Bool {
61 | -999 == code
62 | }
63 | }
64 |
65 | extension Date {
66 |
67 | private var calendar: Calendar {
68 | Calendar.current
69 | }
70 |
71 | var year: Int {
72 | calendar.component(.year, from: self)
73 | }
74 |
75 | }
76 |
--------------------------------------------------------------------------------
/HostsX/Constant.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Constant.swift
3 | // HostsToolForMac
4 | //
5 | // Created by zm on 2019/11/14.
6 | // Copyright © 2019 ZzzM. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | typealias VoidClosure = () -> Void
12 | typealias ReusltClosure = (String) -> Void
13 | typealias HostsClosure = (String, String) -> Void
14 | typealias FailureClosure = (Error?) -> Void
15 | typealias RowClosure = (Int) -> Void
16 |
17 |
18 |
19 | enum HostsPath {
20 | static let hosts = "/private/etc/hosts"
21 | static let temp = URL.temp.path
22 | }
23 |
24 |
25 | enum HostsTag {
26 | static let start = "# My Hosts Start"
27 | static let end = "# My Hosts End"
28 | static let flag = "###"
29 | }
30 |
31 | enum HostsUrl {
32 | static let h1 = "https://scaffrey.coding.net/p/hosts/d/hosts/git/raw/master/hosts-files/hosts"
33 | static let h2 = "https://raw.githubusercontent.com/googlehosts/hosts/master/hosts-files/hosts"
34 | static let github = "https://github.com/ZzzM/HostsX"
35 | }
36 |
37 | extension NSStatusItem {
38 | static let system = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
39 | }
40 |
41 |
42 | extension URL {
43 | static let temp = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
44 | }
45 |
46 | extension NSSize {
47 | static let dialog = NSSize(width: 200, height: 120)
48 | }
49 |
50 | extension NSImage.Name {
51 | static let star = "star"
52 | static let add = "add"
53 | static let link = "link"
54 | static let trash = "trash"
55 | }
56 |
57 |
58 | extension NSStoryboard {
59 | static let remote = NSStoryboard(name: "Remote", bundle: .none)
60 | static let help = NSStoryboard(name: "Help", bundle: .none)
61 | }
62 |
63 | extension NSColor {
64 | @available(macOS 10.13, *)
65 | static let backgroud = NSColor(named: "backgroudColor")
66 | }
67 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 |
2 | name: Deploy
3 |
4 | on:
5 | push:
6 | tags:
7 | - '*'
8 |
9 | workflow_dispatch:
10 |
11 | jobs:
12 |
13 | Deploy:
14 | runs-on: macos-latest
15 | steps:
16 |
17 | - name: Checkout
18 | uses: actions/checkout@v2.4.0
19 |
20 | - name: Package
21 | run: |
22 | fastlane run increment_build_number build_number:${GITHUB_SHA::7}
23 | fastlane package
24 |
25 | - name: Create
26 | run: |
27 | brew install create-dmg
28 |
29 | create-dmg \
30 | --volicon AppIcon.icns \
31 | --hdiutil-quiet \
32 | --app-drop-link 0 30 \
33 | $APP_SCHEME.dmg \
34 | $APP_SCHEME.app
35 |
36 | - name: Update
37 | run: |
38 |
39 | build=`defaults read $PWD/$APP_SCHEME.app/Contents/Info.plist CFBundleVersion`
40 |
41 | signature=`$PWD/sign_update -s $SPARKLE_KEY $APP_SCHEME.dmg`
42 | signature=${signature%\" *}
43 | signature=${signature#*=\"}
44 |
45 | changelog=`cat CHANGELOG.md`
46 | changelog=${changelog%%---*}
47 | changelog=${changelog#*###}
48 | changelog="###$changelog"
49 | content=""
50 |
51 | echo "$changelog$content" > CHANGELOG.md
52 |
53 | - name: Release
54 | uses: softprops/action-gh-release@v1
55 | with:
56 | body_path: CHANGELOG.md
57 | files: ${{ env.APP_SCHEME }}.dmg
58 |
59 | - name: Appcast
60 | run: |
61 | curl -u ZzzM:$GITPAGE_TOKEN -X POST \
62 | https://api.github.com/repos/${{ github.repository }}/pages/builds \
63 | -H "Accept: application/vnd.github.mister-fantastic-preview+json"
64 |
65 | env:
66 | APP_SCHEME: ${{ secrets.APP_SCHEME }}
67 | GITPAGE_TOKEN: ${{ secrets.GITPAGE_TOKEN }}
68 | SPARKLE_KEY: ${{ secrets.SPARKLE_KEY }}
69 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ### Swift ###
2 | # Xcode
3 | #
4 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
5 |
6 | ## User settings
7 | xcuserdata/
8 |
9 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
10 | *.xcscmblueprint
11 | *.xccheckout
12 |
13 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
14 | build/
15 | DerivedData/
16 | *.moved-aside
17 | *.pbxuser
18 | !default.pbxuser
19 | *.mode1v3
20 | !default.mode1v3
21 | *.mode2v3
22 | !default.mode2v3
23 | *.perspectivev3
24 | !default.perspectivev3
25 |
26 | ### Xcode Patch ###
27 | *.xcodeproj/*
28 | !*.xcodeproj/project.pbxproj
29 | !*.xcodeproj/xcshareddata/
30 | !*.xcworkspace/contents.xcworkspacedata
31 | /*.gcno
32 | **/xcshareddata/WorkspaceSettings.xcsettings
33 |
34 | ## Obj-C/Swift specific
35 | *.hmap
36 |
37 | ## App packaging
38 | *.ipa
39 | *.dSYM.zip
40 | *.dSYM
41 |
42 | ## Playgrounds
43 | timeline.xctimeline
44 | playground.xcworkspace
45 |
46 | # Swift Package Manager
47 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
48 | # Packages/
49 | # Package.pins
50 | # Package.resolved
51 | # *.xcodeproj
52 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
53 | # hence it is not needed unless you have added a package configuration file to your project
54 | # .swiftpm
55 |
56 | .build/
57 |
58 | # CocoaPods
59 | # We recommend against adding the Pods directory to your .gitignore. However
60 | # you should judge for yourself, the pros and cons are mentioned at:
61 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
62 | # Pods/
63 | # Add this line if you want to avoid checking in source code from the Xcode workspace
64 | # *.xcworkspace
65 |
66 | # Carthage
67 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
68 | # Carthage/Checkouts
69 |
70 | Carthage/Build/
71 |
72 | # Accio dependency management
73 | Dependencies/
74 | .accio/
75 |
76 | # fastlane
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 | # After new code Injection tools there's a generated folder /iOSInjectionProject
89 | # https://github.com/johnno1962/injectionforxcode
90 |
91 | iOSInjectionProject/
92 |
93 |
94 |
--------------------------------------------------------------------------------
/HostsX/Module/Remote/HostsConfigController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HostsConfigController.swift
3 | // HostsX
4 | //
5 | // Created by zm on 2021/12/6.
6 | //
7 |
8 | import Cocoa
9 |
10 | class HostsConfigController: NSViewController {
11 |
12 | @IBOutlet weak var titleLabel: NSTextField!
13 | @IBOutlet weak var aliasLabel: NSTextField!
14 | @IBOutlet weak var urlLabel: NSTextField!
15 | @IBOutlet weak var aliasPrompt: NSTextField!
16 | @IBOutlet weak var urlPrompt: NSTextField!
17 | @IBOutlet weak var doneButton: NSButton!
18 |
19 | var completion: HostsClosure?, from: NSViewController?
20 |
21 | static func show(_ from: NSViewController?, completion: HostsClosure?) {
22 | let controller = NSStoryboard.remote.instantiateController(self)
23 | controller?.from = from
24 | controller?.completion = completion
25 | from?.setWindowContent(controller)
26 | }
27 |
28 | @IBOutlet weak var aliasFiled: NSTextField!
29 | @IBOutlet weak var urlFiled: NSTextField!
30 |
31 |
32 | override func viewDidLoad() {
33 | super.viewDidLoad()
34 | aliasLabel.stringValue = Localization.Hosts.alias
35 | urlLabel.stringValue = Localization.Hosts.url
36 | doneButton.title = Localization.Remote.hostsOK
37 | titleLabel.stringValue = Localization.Remote.hosts
38 | aliasFiled.placeholderString = Localization.Remote.aliasPlaceholder
39 | urlFiled.placeholderString = Localization.Remote.urlPlaceholder
40 | aliasPrompt.stringValue = Localization.Remote.aliasPrompt
41 | }
42 |
43 | @IBAction func onBack(_ sender: Any) {
44 | setWindowContent(from)
45 | }
46 |
47 | @IBAction func onDone(_ sender: Any) {
48 |
49 | let alias = aliasFiled.stringValue, urlString = urlFiled.stringValue
50 |
51 | if alias.isEmpty {
52 | return showInfo(Localization.Remote.aliasPromptEmpty)
53 | }
54 |
55 | guard 3...8 ~= alias.count else {
56 | return showInfo(Localization.Remote.aliasPromptInvalid)
57 | }
58 |
59 | if RemoteSource.containsAlias(alias) {
60 | return showInfo(Localization.Remote.aliasPromptExists)
61 | }
62 |
63 | if urlString.isEmpty {
64 | return showInfo(Localization.Remote.urlPromptEmpty)
65 | }
66 |
67 | guard urlString.contains("http://") || urlString.contains("https://") else {
68 | return showInfo(Localization.Remote.urlPromptInvalid)
69 | }
70 |
71 | if RemoteSource.containsURL(urlString) {
72 | return showInfo(Localization.Remote.urlPromptExists)
73 | }
74 | setWindowContent(from)
75 |
76 | completion?(alias, urlString)
77 | }
78 |
79 | }
80 |
81 |
82 |
83 |
--------------------------------------------------------------------------------
/HostsX/Service/Localization.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Localization.swift
3 | // HostsX
4 | //
5 | // Created by zm on 2021/12/17.
6 | //
7 |
8 | import Foundation
9 |
10 | struct Localization {
11 |
12 | enum About {
13 | static let version = "about.version".localized
14 | }
15 |
16 | enum Error {
17 | static let invalidHosts = "error.invalidHosts".localized
18 | static let invalidURL = "error.invalidURL".localized
19 | static let compile = "error.compile".localized
20 | static let execute = "error.execute".localized
21 | static let cancelled = "error.cancelled".localized
22 | }
23 |
24 | enum Update {
25 | static let finished = "update.finished".localized
26 | static let succeeded = "update.succeeded".localized
27 | static let failed = "update.failed".localized
28 | static let unfinished = "update.unfinished".localized
29 | }
30 |
31 | enum Menu {
32 | static let local = "menu.local".localized
33 | static let remote = "menu.remote".localized
34 | static let remoteDownload = "menu.remote.download".localized
35 | static let remoteConfig = "menu.remote.config".localized
36 | static let help = "menu.help".localized
37 | static let helpCheck = "menu.help.check".localized
38 | static let helpAbout = "menu.help.about".localized
39 | static let quit = "menu.quit".localized
40 | }
41 |
42 | enum Hosts {
43 | static let alias = "hosts.alias".localized
44 | static let url = "hosts.url".localized
45 | static let origin = "hosts.origin".localized
46 | static let available = "hosts.available".localized
47 | static let unavailable = "hosts.unavailable".localized
48 | static let unknown = "hosts.unknown".localized
49 | }
50 |
51 | enum Dialog {
52 | static let cancel = "dialog.cancel".localized
53 | static let done = "dialog.done".localized
54 | }
55 |
56 |
57 | enum Remote {
58 | static let hosts = "remote.hosts".localized
59 | static let hostsOK = "remote.hosts.ok".localized
60 | static let aliasPrompt = "remote.alias.prompt".localized
61 | static let aliasPlaceholder = "remote.alias.placeholder".localized
62 | static let urlPlaceholder = "remote.url.placeholder".localized
63 |
64 |
65 | static let aliasPromptEmpty = "remote.alias.prompt.empty".localized
66 | static let aliasPromptInvalid = "remote.alias.prompt.invalid".localized
67 | static let aliasPromptExists = "remote.alias.prompt.exists".localized
68 |
69 | static let urlPromptEmpty = "remote.url.prompt.empty".localized
70 | static let urlPromptInvalid = "remote.url.prompt.invalid".localized
71 | static let urlPromptExists = "remote.url.prompt.exists".localized
72 |
73 | static func hostsConfirmOpen(_ alias: String) -> String {
74 | "remote.hosts.confirm.open".localized(alias)
75 | }
76 | static func hostsConfirmRemove(_ alias: String) -> String {
77 | "remote.hosts.confirm.remove".localized(alias)
78 | }
79 | static func hostsConfirmOrigin(_ alias: String) -> String {
80 | "remote.hosts.confirm.origin".localized(alias)
81 | }
82 | }
83 | }
84 |
85 |
--------------------------------------------------------------------------------
/HostsX/Service/RemoteSource.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RemoteSource.swift
3 | // HostsX
4 | //
5 | // Created by zm on 2021/12/7.
6 | //
7 |
8 | import Foundation
9 | import AppKit
10 |
11 | struct RemoteSource {
12 |
13 | private enum RemoteSourceKey {
14 | static let data = "remoteSourceKey.data"
15 | }
16 |
17 | private static let defaultData = [
18 | Hosts("Hosts01", url: HostsUrl.h1, isOrigin: true),
19 | Hosts("Hosts02", url: HostsUrl.h2)
20 | ]
21 |
22 | private static var data: [Hosts] {
23 | set {
24 | let encoder = JSONEncoder()
25 | if let data = try? encoder.encode(newValue) {
26 | UserDefaults.standard.setValue(data, forKey: RemoteSourceKey.data)
27 | }
28 | }
29 | get {
30 | guard let data = UserDefaults.standard.value(forKey: RemoteSourceKey.data) as? Data else {
31 | return defaultData
32 | }
33 | let decoder = JSONDecoder()
34 | if let result = try? decoder.decode([Hosts].self, from: data) {
35 | return result
36 | }
37 | return defaultData
38 | }
39 | }
40 |
41 | private static var row = 0
42 | private static var current: Hosts { self[row] }
43 | private static var origin: Hosts { data.first(where: {$0.isOrigin}) ?? data[0]}
44 |
45 | static var originUrl: String { origin.url }
46 | static var currentAlias: String { current.alias }
47 | static var rows: Int { data.count }
48 |
49 | static var canAdd: Bool { rows <= 10 }
50 | static var canRemove: Bool { !current.isOrigin }
51 | static var canSetAsOrigin: Bool { current.isAvailable && canRemove }
52 |
53 | }
54 |
55 |
56 | extension RemoteSource {
57 |
58 | static subscript(_ row: Int) -> Hosts {
59 | get { data[row] }
60 | set { data[row] = newValue }
61 | }
62 |
63 | static func cancel() { Network.cancel() }
64 |
65 | static func select(_ row: Int, completion: VoidClosure? = .none) {
66 | guard row > -1 else { return }
67 | self.row = row
68 | completion?()
69 | }
70 |
71 | static func check(completion: @escaping (Hosts) -> Void) {
72 | completion(current)
73 | Network.check(current.url) {
74 | let hosts = current
75 | hosts.status = $0
76 | self[row] = hosts
77 | completion(current)
78 | }
79 | }
80 |
81 | @discardableResult
82 | static func removed() -> Int {
83 | let _row = row
84 | data.remove(at: _row)
85 | row = 0
86 | return _row
87 | }
88 |
89 |
90 | static func insert(_ alias: String, urlString: String) {
91 | data.insert(Hosts(alias, url: urlString), at: 1)
92 | row = 1
93 | }
94 |
95 | static func setAsOrigin() {
96 |
97 | let hosts = current
98 | hosts.isOrigin.toggle()
99 | removed()
100 |
101 | for (i, val) in data.enumerated() {
102 | val.isOrigin.toggle()
103 | data[i] = val
104 | }
105 |
106 | data.insert(hosts, at: 0)
107 | }
108 |
109 | static func restore() {
110 | data = defaultData
111 | row = 0
112 | }
113 |
114 | static func open() {
115 | NSWorkspace.open(current.url)
116 | }
117 |
118 | static func containsAlias(_ alias: String) -> Bool {
119 | data.contains(where: {$0.alias == alias })
120 | }
121 |
122 | static func containsURL(_ url: String) -> Bool {
123 | data.contains(where: {$0.url == url })
124 | }
125 | }
126 |
127 |
--------------------------------------------------------------------------------
/HostsX/Module/Remote/RemoteConfigController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RemoteConfigController.swift
3 | // HostsX
4 | //
5 | // Created by zm on 2021/12/2.
6 | //
7 |
8 | import Cocoa
9 |
10 | class RemoteConfigController: NSViewController {
11 |
12 | @IBOutlet weak var tableView: NSTableView!
13 |
14 | @IBOutlet weak var aliasLabel: NSTextField!
15 |
16 | @IBOutlet weak var statusIcon: NSImageView!
17 |
18 | @IBOutlet weak var originButton: NSButton!
19 |
20 | @IBOutlet var remoteMenu: NSMenu!
21 |
22 | override func viewDidDisappear() {
23 | super.viewDidDisappear()
24 | RemoteSource.cancel()
25 | }
26 |
27 | override func viewDidLoad() {
28 | super.viewDidLoad()
29 | tableView.setup()
30 | showHosts()
31 | setupMenu()
32 | }
33 |
34 | @IBAction func onSetAsOrigin(_ sender: Any) {
35 | let message = Localization.Remote.hostsConfirmOrigin(RemoteSource.currentAlias)
36 | showConfirm(message) { [self] in
37 | RemoteSource.setAsOrigin()
38 | originButton.isHidden = true
39 | tableView.moved()
40 | }
41 | }
42 |
43 | @objc func open() {
44 | let message = Localization.Remote.hostsConfirmOpen(RemoteSource.currentAlias)
45 | showConfirm(message, completion: RemoteSource.open)
46 | }
47 |
48 | @objc func remove() {
49 | let message = Localization.Remote.hostsConfirmRemove(RemoteSource.currentAlias)
50 | showConfirm(message) { [self] in
51 | let row = RemoteSource.removed()
52 | tableView.removed(row)
53 | }
54 | }
55 |
56 | @objc func insert() {
57 | HostsConfigController.show(self) { [self] in
58 | RemoteSource.insert($0, urlString: $1)
59 | tableView.inserted()
60 | }
61 | }
62 |
63 | }
64 |
65 | extension RemoteConfigController {
66 |
67 | private func setupMenu() {
68 | remoteMenu.addItemIcon(.link, action: #selector(open))
69 | remoteMenu.addItemIcon(.add, action: #selector(insert))
70 | remoteMenu.addItemIcon(.trash, action: #selector(remove))
71 | }
72 |
73 | private func showHosts() {
74 | RemoteSource.check { hosts in
75 | DispatchQueue.main.async { [weak self] in
76 | guard let strong = self else { return }
77 | strong.aliasLabel.stringValue = hosts.alias
78 | strong.statusIcon.image = hosts.status.icon
79 | strong.originButton.isHidden = !RemoteSource.canSetAsOrigin
80 | }
81 | }
82 | }
83 |
84 | }
85 |
86 | extension RemoteConfigController: NSTableViewDelegate, NSTableViewDataSource {
87 |
88 | func numberOfRows(in tableView: NSTableView) -> Int {
89 | RemoteSource.rows
90 | }
91 |
92 | func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
93 | guard let cell = tableView.makeView(type: HostsURLCell.self) else { return .none }
94 | cell.hosts = RemoteSource[row]
95 | return cell
96 | }
97 |
98 | func tableViewSelectionDidChange(_ notification: Notification) {
99 | guard let _tableView = notification.object as? NSTableView else { return }
100 | RemoteSource.select(_tableView.selectedRow)
101 | showHosts()
102 | }
103 |
104 | }
105 |
106 | extension RemoteConfigController: NSMenuDelegate {
107 | func menuWillOpen(_ menu: NSMenu) {
108 | RemoteSource.select(tableView.clickedRow)
109 | remoteMenu.item(at: 1)?.isHidden = !RemoteSource.canAdd
110 | remoteMenu.item(at: 2)?.isHidden = !RemoteSource.canRemove
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/HostsX/Service/FileHelper.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FileHelper.swift
3 | // HostsX
4 | //
5 | // Created by zm on 2021/12/20.
6 | //
7 |
8 | import Foundation
9 | import AppKit
10 |
11 | struct FileHelper {
12 |
13 | static func localUpdate(completion: @escaping FailureClosure) {
14 | guard let url = open() else {
15 | return completion(HostsError.cancelled)
16 | }
17 | read(url, failure: completion) {
18 | do {
19 | try check($0)
20 | try write(to: .temp, contents: $0)
21 | try copy(atPath: HostsPath.temp, toPath: HostsPath.hosts)
22 | completion(.none)
23 | } catch {
24 | completion(error)
25 | }
26 | }
27 | }
28 |
29 | static func remoteUpdate(completion: @escaping FailureClosure) {
30 | guard let url = URL(string: RemoteSource.originUrl) else {
31 | return completion(HostsError.invalidURL)
32 | }
33 | read(url, failure: completion) {
34 | do {
35 | try check($0)
36 | try write(to: .temp, contents: join($0))
37 | try copy(atPath: HostsPath.temp, toPath: HostsPath.hosts)
38 | completion(.none)
39 | } catch {
40 | completion(error)
41 | }
42 | }
43 | }
44 |
45 | }
46 |
47 | extension FileHelper {
48 |
49 |
50 | private static func write(to url: URL, contents: String) throws {
51 | try contents.write(to: url, atomically: true, encoding: .utf8)
52 | }
53 |
54 | private static func remove(at url: URL) throws {
55 | try FileManager.default.removeItem(at: url)
56 | }
57 |
58 | private static func read(_ url: URL,
59 | failure: @escaping FailureClosure,
60 | success: @escaping ReusltClosure) {
61 | DispatchQueue.global().async {
62 | do {
63 | //sleep(5)
64 | let content = try String(contentsOf: url)
65 | DispatchQueue.main.async {
66 | success(content)
67 | }
68 | } catch {
69 | DispatchQueue.main.async {
70 | failure(error)
71 | }
72 | }
73 | }
74 | }
75 |
76 | private static func open() -> URL? {
77 | let _openPanel = NSOpenPanel()
78 | _openPanel.allowsMultipleSelection = false
79 | _openPanel.canChooseDirectories = false
80 | _openPanel.canCreateDirectories = false
81 | _openPanel.canChooseFiles = true
82 | return _openPanel.runModal() == .OK ? _openPanel.url : .none
83 | }
84 |
85 | private static func copy(atPath: String, toPath: String) throws {
86 | let appleScript = NSAppleScript(command: "cp -f \(atPath) \(toPath)")
87 | try appleScript.doShell()
88 | }
89 |
90 | private static func check(_ content: String) throws {
91 | guard content.contains("localhost"), content.contains("127.0.0.1") else {
92 | throw HostsError.invalidHosts
93 | }
94 | }
95 |
96 | private static func join(_ content: String) throws -> String {
97 |
98 | let joined = """
99 | \(HostsTag.start)
100 |
101 | \(HostsTag.flag)
102 |
103 | \(HostsTag.end)
104 |
105 | \(content)
106 | """
107 | guard
108 | let hosts = try? String(contentsOfFile: HostsPath.hosts),
109 | let startIndex = hosts.range(of: HostsTag.start)?.upperBound,
110 | let endIndex = hosts.range(of: HostsTag.end)?.lowerBound else{
111 | return joined
112 | }
113 |
114 | let myHosts = hosts[startIndex.. Bool {
99 | true
100 | }
101 | }
102 |
103 | @available(macOS 10.14, *)
104 | extension NotificationDelegate: UNUserNotificationCenterDelegate {
105 | func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
106 | if #available(macOS 11.0, *) {
107 | completionHandler([.banner, .sound])
108 | } else {
109 | completionHandler([.alert, .sound])
110 | }
111 | }
112 | }
113 |
114 |
115 |
--------------------------------------------------------------------------------
/HostsX/Component/Dialog/InfoDialogController.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/HostsX/Extension/AppKit+.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppKit+.swift
3 | // HostsX
4 | //
5 | // Created by zm on 2021/12/15.
6 | //
7 |
8 | import AppKit
9 |
10 | extension Bundle {
11 | var bundleName: String? {
12 | infoDictionary?["CFBundleName"] as? String
13 | }
14 |
15 | var shortVersion: String? {
16 | infoDictionary?["CFBundleShortVersionString"] as? String
17 | }
18 |
19 | var version: String? {
20 | infoDictionary?["CFBundleVersion"] as? String
21 | }
22 |
23 | var humanReadableCopyright: String? {
24 | infoDictionary?["NSHumanReadableCopyright"] as? String
25 | }
26 |
27 | }
28 |
29 | extension NSWorkspace {
30 | class func open(_ urlString: String) {
31 | guard let url = URL(string: urlString) else { return }
32 | shared.open(url)
33 | }
34 | }
35 |
36 |
37 | extension NSTableView {
38 |
39 | func makeView(type: T.Type) -> T? {
40 | let identifier = NSUserInterfaceItemIdentifier(rawValue: T.className)
41 | return makeView(withIdentifier: identifier, owner: self) as? T
42 | }
43 |
44 | func setup() {
45 | headerView = .none
46 | select(0)
47 | }
48 |
49 | func moved() {
50 | move(selectedRow)
51 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { [self] in
52 | reloadData()
53 | select(0)
54 | }
55 | }
56 |
57 | func removed(_ row: Int) {
58 | remove(row)
59 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { [self] in
60 | reloadData()
61 | select(0)
62 | }
63 | }
64 |
65 | func inserted() {
66 | insert(1)
67 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { [self] in
68 | reloadData()
69 | select(1)
70 | }
71 | }
72 |
73 | private func select(_ row: Int) {
74 | selectRowIndexes(IndexSet(integer: row), byExtendingSelection: false)
75 | }
76 |
77 | private func insert(_ row: Int) {
78 | beginUpdates()
79 | insertRows(at: IndexSet(integer: row), withAnimation: .slideLeft)
80 | endUpdates()
81 | }
82 |
83 | private func remove(_ row: Int) {
84 | beginUpdates()
85 | removeRows(at: IndexSet(integer: row), withAnimation: .slideRight)
86 | endUpdates()
87 | }
88 |
89 | private func move(_ row: Int) {
90 | beginUpdates()
91 | moveRow(at: row, to: 0)
92 | endUpdates()
93 | }
94 |
95 | }
96 |
97 | extension NSView {
98 |
99 | @IBInspectable
100 | var cornerRadius: CGFloat {
101 | get { layer?.cornerRadius ?? 0 }
102 | set {
103 | wantsLayer = true
104 | layer?.masksToBounds = true
105 | layer?.cornerRadius = abs(CGFloat(Int(newValue * 100)) / 100)
106 | }
107 | }
108 | }
109 |
110 |
111 | extension NSViewController {
112 |
113 | func setWindowContent(_ controller: NSViewController?) {
114 | view.window?.contentViewController = controller
115 | }
116 |
117 | func showConfirm(_ message: String, completion: @escaping VoidClosure) {
118 | Dialog.showConfirm(from: self, message: message, completion: completion)
119 | }
120 |
121 | func showInfo(_ message: String) {
122 | Dialog.showInfo(from: self, message:message)
123 | }
124 |
125 | }
126 |
127 | extension NSStoryboard {
128 | func instantiateController(_ type: T.Type) -> T? {
129 | instantiateController(withIdentifier: type.className) as? T
130 | }
131 |
132 | }
133 |
134 | extension NSMenu {
135 | func addItemIcon(_ name: String, action: Selector?) {
136 | let item = NSMenuItem(title: "", action: action, keyEquivalent: "")
137 | let icon = NSImage(named: name)
138 | icon?.isTemplate = true
139 | item.image = icon
140 | addItem(item)
141 | }
142 | }
143 |
144 |
145 |
146 | extension NSStatusItem {
147 |
148 | func setMenuBarIcon() {
149 | guard let icon = NSImage(named: NSImage.Name("menuBarIcon")) else { return }
150 | icon.isTemplate = true
151 | icon.size = CGSize(width: 16, height: 16)
152 | button?.image = icon
153 | }
154 |
155 | }
156 |
157 | extension Optional where Wrapped == NSWindowController {
158 | mutating func show(_ content: NSStoryboard) {
159 | self?.close()
160 | self = content.instantiateInitialController() as? NSWindowController
161 | self?.showWindow(.none)
162 | NSApp.activate(ignoringOtherApps: true)
163 | }
164 | }
165 |
--------------------------------------------------------------------------------
/HostsX/Menu.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
86 |
87 |
88 |
89 |
--------------------------------------------------------------------------------
/HostsX/Component/Dialog/ConfirmDialogController.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
37 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: https://rubygems.org/
3 | specs:
4 | CFPropertyList (3.0.5)
5 | rexml
6 | addressable (2.8.0)
7 | public_suffix (>= 2.0.2, < 5.0)
8 | artifactory (3.0.15)
9 | atomos (0.1.3)
10 | aws-eventstream (1.2.0)
11 | aws-partitions (1.543.0)
12 | aws-sdk-core (3.125.0)
13 | aws-eventstream (~> 1, >= 1.0.2)
14 | aws-partitions (~> 1, >= 1.525.0)
15 | aws-sigv4 (~> 1.1)
16 | jmespath (~> 1.0)
17 | aws-sdk-kms (1.53.0)
18 | aws-sdk-core (~> 3, >= 3.125.0)
19 | aws-sigv4 (~> 1.1)
20 | aws-sdk-s3 (1.110.0)
21 | aws-sdk-core (~> 3, >= 3.125.0)
22 | aws-sdk-kms (~> 1)
23 | aws-sigv4 (~> 1.4)
24 | aws-sigv4 (1.4.0)
25 | aws-eventstream (~> 1, >= 1.0.2)
26 | babosa (1.0.4)
27 | claide (1.0.3)
28 | colored (1.2)
29 | colored2 (3.1.2)
30 | commander (4.6.0)
31 | highline (~> 2.0.0)
32 | declarative (0.0.20)
33 | digest-crc (0.6.4)
34 | rake (>= 12.0.0, < 14.0.0)
35 | domain_name (0.5.20190701)
36 | unf (>= 0.0.5, < 1.0.0)
37 | dotenv (2.7.6)
38 | emoji_regex (3.2.3)
39 | excon (0.89.0)
40 | faraday (1.8.0)
41 | faraday-em_http (~> 1.0)
42 | faraday-em_synchrony (~> 1.0)
43 | faraday-excon (~> 1.1)
44 | faraday-httpclient (~> 1.0.1)
45 | faraday-net_http (~> 1.0)
46 | faraday-net_http_persistent (~> 1.1)
47 | faraday-patron (~> 1.0)
48 | faraday-rack (~> 1.0)
49 | multipart-post (>= 1.2, < 3)
50 | ruby2_keywords (>= 0.0.4)
51 | faraday-cookie_jar (0.0.7)
52 | faraday (>= 0.8.0)
53 | http-cookie (~> 1.0.0)
54 | faraday-em_http (1.0.0)
55 | faraday-em_synchrony (1.0.0)
56 | faraday-excon (1.1.0)
57 | faraday-httpclient (1.0.1)
58 | faraday-net_http (1.0.1)
59 | faraday-net_http_persistent (1.2.0)
60 | faraday-patron (1.0.0)
61 | faraday-rack (1.0.0)
62 | faraday_middleware (1.2.0)
63 | faraday (~> 1.0)
64 | fastimage (2.2.6)
65 | fastlane (2.199.0)
66 | CFPropertyList (>= 2.3, < 4.0.0)
67 | addressable (>= 2.8, < 3.0.0)
68 | artifactory (~> 3.0)
69 | aws-sdk-s3 (~> 1.0)
70 | babosa (>= 1.0.3, < 2.0.0)
71 | bundler (>= 1.12.0, < 3.0.0)
72 | colored
73 | commander (~> 4.6)
74 | dotenv (>= 2.1.1, < 3.0.0)
75 | emoji_regex (>= 0.1, < 4.0)
76 | excon (>= 0.71.0, < 1.0.0)
77 | faraday (~> 1.0)
78 | faraday-cookie_jar (~> 0.0.6)
79 | faraday_middleware (~> 1.0)
80 | fastimage (>= 2.1.0, < 3.0.0)
81 | gh_inspector (>= 1.1.2, < 2.0.0)
82 | google-apis-androidpublisher_v3 (~> 0.3)
83 | google-apis-playcustomapp_v1 (~> 0.1)
84 | google-cloud-storage (~> 1.31)
85 | highline (~> 2.0)
86 | json (< 3.0.0)
87 | jwt (>= 2.1.0, < 3)
88 | mini_magick (>= 4.9.4, < 5.0.0)
89 | multipart-post (~> 2.0.0)
90 | naturally (~> 2.2)
91 | optparse (~> 0.1.1)
92 | plist (>= 3.1.0, < 4.0.0)
93 | rubyzip (>= 2.0.0, < 3.0.0)
94 | security (= 0.1.3)
95 | simctl (~> 1.6.3)
96 | terminal-notifier (>= 2.0.0, < 3.0.0)
97 | terminal-table (>= 1.4.5, < 2.0.0)
98 | tty-screen (>= 0.6.3, < 1.0.0)
99 | tty-spinner (>= 0.8.0, < 1.0.0)
100 | word_wrap (~> 1.0.0)
101 | xcodeproj (>= 1.13.0, < 2.0.0)
102 | xcpretty (~> 0.3.0)
103 | xcpretty-travis-formatter (>= 0.0.3)
104 | fastlane-plugin-dmg (0.1.1)
105 | fastlane-plugin-versioning (0.5.0)
106 | gh_inspector (1.1.3)
107 | google-apis-androidpublisher_v3 (0.14.0)
108 | google-apis-core (>= 0.4, < 2.a)
109 | google-apis-core (0.4.1)
110 | addressable (~> 2.5, >= 2.5.1)
111 | googleauth (>= 0.16.2, < 2.a)
112 | httpclient (>= 2.8.1, < 3.a)
113 | mini_mime (~> 1.0)
114 | representable (~> 3.0)
115 | retriable (>= 2.0, < 4.a)
116 | rexml
117 | webrick
118 | google-apis-iamcredentials_v1 (0.9.0)
119 | google-apis-core (>= 0.4, < 2.a)
120 | google-apis-playcustomapp_v1 (0.6.0)
121 | google-apis-core (>= 0.4, < 2.a)
122 | google-apis-storage_v1 (0.10.0)
123 | google-apis-core (>= 0.4, < 2.a)
124 | google-cloud-core (1.6.0)
125 | google-cloud-env (~> 1.0)
126 | google-cloud-errors (~> 1.0)
127 | google-cloud-env (1.5.0)
128 | faraday (>= 0.17.3, < 2.0)
129 | google-cloud-errors (1.2.0)
130 | google-cloud-storage (1.35.0)
131 | addressable (~> 2.8)
132 | digest-crc (~> 0.4)
133 | google-apis-iamcredentials_v1 (~> 0.1)
134 | google-apis-storage_v1 (~> 0.1)
135 | google-cloud-core (~> 1.6)
136 | googleauth (>= 0.16.2, < 2.a)
137 | mini_mime (~> 1.0)
138 | googleauth (1.1.0)
139 | faraday (>= 0.17.3, < 2.0)
140 | jwt (>= 1.4, < 3.0)
141 | memoist (~> 0.16)
142 | multi_json (~> 1.11)
143 | os (>= 0.9, < 2.0)
144 | signet (>= 0.16, < 2.a)
145 | highline (2.0.3)
146 | http-cookie (1.0.4)
147 | domain_name (~> 0.5)
148 | httpclient (2.8.3)
149 | jmespath (1.4.0)
150 | json (2.6.1)
151 | jwt (2.3.0)
152 | memoist (0.16.2)
153 | mini_magick (4.11.0)
154 | mini_mime (1.1.2)
155 | multi_json (1.15.0)
156 | multipart-post (2.0.0)
157 | nanaimo (0.3.0)
158 | naturally (2.2.1)
159 | optparse (0.1.1)
160 | os (1.1.4)
161 | plist (3.6.0)
162 | public_suffix (4.0.6)
163 | rake (13.0.6)
164 | representable (3.1.1)
165 | declarative (< 0.1.0)
166 | trailblazer-option (>= 0.1.1, < 0.2.0)
167 | uber (< 0.2.0)
168 | retriable (3.1.2)
169 | rexml (3.2.5)
170 | rouge (2.0.7)
171 | ruby2_keywords (0.0.5)
172 | rubyzip (2.3.2)
173 | security (0.1.3)
174 | signet (0.16.0)
175 | addressable (~> 2.8)
176 | faraday (>= 0.17.3, < 2.0)
177 | jwt (>= 1.5, < 3.0)
178 | multi_json (~> 1.10)
179 | simctl (1.6.8)
180 | CFPropertyList
181 | naturally
182 | terminal-notifier (2.0.0)
183 | terminal-table (1.8.0)
184 | unicode-display_width (~> 1.1, >= 1.1.1)
185 | trailblazer-option (0.1.2)
186 | tty-cursor (0.7.1)
187 | tty-screen (0.8.1)
188 | tty-spinner (0.9.3)
189 | tty-cursor (~> 0.7)
190 | uber (0.1.0)
191 | unf (0.1.4)
192 | unf_ext
193 | unf_ext (0.0.8)
194 | unicode-display_width (1.8.0)
195 | webrick (1.7.0)
196 | word_wrap (1.0.0)
197 | xcodeproj (1.21.0)
198 | CFPropertyList (>= 2.3.3, < 4.0)
199 | atomos (~> 0.1.3)
200 | claide (>= 1.0.2, < 2.0)
201 | colored2 (~> 3.1)
202 | nanaimo (~> 0.3.0)
203 | rexml (~> 3.2.4)
204 | xcpretty (0.3.0)
205 | rouge (~> 2.0.7)
206 | xcpretty-travis-formatter (1.0.1)
207 | xcpretty (~> 0.2, >= 0.0.7)
208 |
209 | PLATFORMS
210 | ruby
211 |
212 | DEPENDENCIES
213 | fastlane
214 | fastlane-plugin-dmg
215 | fastlane-plugin-versioning
216 |
217 | BUNDLED WITH
218 | 1.17.2
219 |
--------------------------------------------------------------------------------
/HostsX/Help.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
--------------------------------------------------------------------------------
/HostsX.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 55;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 9828C8372772C3A800CC87BC /* NotificationHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9828C8362772C3A800CC87BC /* NotificationHelper.swift */; };
11 | 983E5367277367FD00CE84DE /* Help.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 983E5366277367FD00CE84DE /* Help.storyboard */; };
12 | 983E53692773696500CE84DE /* AboutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 983E53682773696500CE84DE /* AboutViewController.swift */; };
13 | 98526DC52769C40E00D84F87 /* Foundition+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98526DC42769C40E00D84F87 /* Foundition+.swift */; };
14 | 98526DC72769C44800D84F87 /* AppKit+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98526DC62769C44800D84F87 /* AppKit+.swift */; };
15 | 98706F6A276C37B500037F77 /* Menu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 98706F69276C37B500037F77 /* Menu.xib */; };
16 | 98706F6C276C5EC000037F77 /* AppMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98706F6B276C5EC000037F77 /* AppMenu.swift */; };
17 | 98706F71276C78EF00037F77 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 98706F73276C78EF00037F77 /* Localizable.strings */; };
18 | 98706F76276C7C3600037F77 /* Localization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98706F75276C7C3600037F77 /* Localization.swift */; };
19 | 98706F7927705DC700037F77 /* FileHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98706F7827705DC700037F77 /* FileHelper.swift */; };
20 | 98706F7B2770684B00037F77 /* HostsError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98706F7A2770684B00037F77 /* HostsError.swift */; };
21 | 98A019E8275DFA220061CE7D /* ConfirmDialogController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 98A019E2275DFA220061CE7D /* ConfirmDialogController.xib */; };
22 | 98A019E9275DFA220061CE7D /* ConfirmDialogController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98A019E3275DFA220061CE7D /* ConfirmDialogController.swift */; };
23 | 98A019EA275DFA220061CE7D /* InfoDialogController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 98A019E4275DFA220061CE7D /* InfoDialogController.xib */; };
24 | 98A019EB275DFA220061CE7D /* HostsConfigController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98A019E5275DFA220061CE7D /* HostsConfigController.swift */; };
25 | 98A019EC275DFA220061CE7D /* InfoDialogController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98A019E6275DFA220061CE7D /* InfoDialogController.swift */; };
26 | 98A019EE275E01730061CE7D /* Dialog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98A019ED275E01730061CE7D /* Dialog.swift */; };
27 | 98A019F1275F3EA20061CE7D /* RemoteSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98A019F0275F3EA20061CE7D /* RemoteSource.swift */; };
28 | 98D573F12758A0E2001C2D8C /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98D573F02758A0E2001C2D8C /* AppDelegate.swift */; };
29 | 98D573F32758A0E2001C2D8C /* RemoteConfigController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98D573F22758A0E2001C2D8C /* RemoteConfigController.swift */; };
30 | 98D573F52758A0E4001C2D8C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 98D573F42758A0E4001C2D8C /* Assets.xcassets */; };
31 | 98D573F82758A0E4001C2D8C /* Remote.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 98D573F62758A0E4001C2D8C /* Remote.storyboard */; };
32 | 98D574012758C093001C2D8C /* HostsURLCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98D574002758C093001C2D8C /* HostsURLCell.swift */; };
33 | 98D574032758CECF001C2D8C /* Network.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98D574022758CECF001C2D8C /* Network.swift */; };
34 | 98D929CF2774211F00CC4E42 /* HostsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98D929CE2774211F00CC4E42 /* HostsView.swift */; };
35 | 98EFEEB4275A61BA00B996C0 /* Hosts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98EFEEB3275A61BA00B996C0 /* Hosts.swift */; };
36 | 98F449AA275DA5E400BFBE87 /* Constant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98F449A9275DA5E400BFBE87 /* Constant.swift */; };
37 | /* End PBXBuildFile section */
38 |
39 | /* Begin PBXFileReference section */
40 | 9828C8362772C3A800CC87BC /* NotificationHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationHelper.swift; sourceTree = ""; };
41 | 983E5366277367FD00CE84DE /* Help.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Help.storyboard; sourceTree = ""; };
42 | 983E53682773696500CE84DE /* AboutViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutViewController.swift; sourceTree = ""; };
43 | 98526DC42769C40E00D84F87 /* Foundition+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Foundition+.swift"; sourceTree = ""; };
44 | 98526DC62769C44800D84F87 /* AppKit+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppKit+.swift"; sourceTree = ""; };
45 | 98706F69276C37B500037F77 /* Menu.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = Menu.xib; sourceTree = ""; };
46 | 98706F6B276C5EC000037F77 /* AppMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppMenu.swift; sourceTree = ""; };
47 | 98706F72276C78EF00037F77 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; };
48 | 98706F74276C791800037F77 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; };
49 | 98706F75276C7C3600037F77 /* Localization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Localization.swift; sourceTree = ""; };
50 | 98706F7827705DC700037F77 /* FileHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileHelper.swift; sourceTree = ""; };
51 | 98706F7A2770684B00037F77 /* HostsError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostsError.swift; sourceTree = ""; };
52 | 98A019E2275DFA220061CE7D /* ConfirmDialogController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ConfirmDialogController.xib; sourceTree = ""; };
53 | 98A019E3275DFA220061CE7D /* ConfirmDialogController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfirmDialogController.swift; sourceTree = ""; };
54 | 98A019E4275DFA220061CE7D /* InfoDialogController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = InfoDialogController.xib; sourceTree = ""; };
55 | 98A019E5275DFA220061CE7D /* HostsConfigController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HostsConfigController.swift; sourceTree = ""; };
56 | 98A019E6275DFA220061CE7D /* InfoDialogController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InfoDialogController.swift; sourceTree = ""; };
57 | 98A019ED275E01730061CE7D /* Dialog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Dialog.swift; sourceTree = ""; };
58 | 98A019F0275F3EA20061CE7D /* RemoteSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteSource.swift; sourceTree = ""; };
59 | 98D573ED2758A0E2001C2D8C /* HostsX.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = HostsX.app; sourceTree = BUILT_PRODUCTS_DIR; };
60 | 98D573F02758A0E2001C2D8C /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
61 | 98D573F22758A0E2001C2D8C /* RemoteConfigController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteConfigController.swift; sourceTree = ""; };
62 | 98D573F42758A0E4001C2D8C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
63 | 98D573F72758A0E4001C2D8C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Remote.storyboard; sourceTree = ""; };
64 | 98D573F92758A0E4001C2D8C /* HostsX.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = HostsX.entitlements; sourceTree = ""; };
65 | 98D574002758C093001C2D8C /* HostsURLCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostsURLCell.swift; sourceTree = ""; };
66 | 98D574022758CECF001C2D8C /* Network.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Network.swift; sourceTree = ""; };
67 | 98D574042758D349001C2D8C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; };
68 | 98D929CE2774211F00CC4E42 /* HostsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostsView.swift; sourceTree = ""; };
69 | 98EFEEB3275A61BA00B996C0 /* Hosts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Hosts.swift; sourceTree = ""; };
70 | 98F449A9275DA5E400BFBE87 /* Constant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Constant.swift; sourceTree = ""; };
71 | /* End PBXFileReference section */
72 |
73 | /* Begin PBXFrameworksBuildPhase section */
74 | 98D573EA2758A0E2001C2D8C /* Frameworks */ = {
75 | isa = PBXFrameworksBuildPhase;
76 | buildActionMask = 2147483647;
77 | files = (
78 | );
79 | runOnlyForDeploymentPostprocessing = 0;
80 | };
81 | /* End PBXFrameworksBuildPhase section */
82 |
83 | /* Begin PBXGroup section */
84 | 983E5361277367AD00CE84DE /* Help */ = {
85 | isa = PBXGroup;
86 | children = (
87 | 983E53682773696500CE84DE /* AboutViewController.swift */,
88 | );
89 | path = Help;
90 | sourceTree = "";
91 | };
92 | 98A019E0275DFA220061CE7D /* Dialog */ = {
93 | isa = PBXGroup;
94 | children = (
95 | 98A019E3275DFA220061CE7D /* ConfirmDialogController.swift */,
96 | 98A019E2275DFA220061CE7D /* ConfirmDialogController.xib */,
97 | 98A019E6275DFA220061CE7D /* InfoDialogController.swift */,
98 | 98A019E4275DFA220061CE7D /* InfoDialogController.xib */,
99 | 98A019ED275E01730061CE7D /* Dialog.swift */,
100 | );
101 | path = Dialog;
102 | sourceTree = "";
103 | };
104 | 98A019EF275F3E890061CE7D /* Service */ = {
105 | isa = PBXGroup;
106 | children = (
107 | 98D574022758CECF001C2D8C /* Network.swift */,
108 | 98A019F0275F3EA20061CE7D /* RemoteSource.swift */,
109 | 98706F75276C7C3600037F77 /* Localization.swift */,
110 | 98706F7827705DC700037F77 /* FileHelper.swift */,
111 | 9828C8362772C3A800CC87BC /* NotificationHelper.swift */,
112 | );
113 | path = Service;
114 | sourceTree = "";
115 | };
116 | 98A019F2276094690061CE7D /* Extension */ = {
117 | isa = PBXGroup;
118 | children = (
119 | 98526DC42769C40E00D84F87 /* Foundition+.swift */,
120 | 98526DC62769C44800D84F87 /* AppKit+.swift */,
121 | );
122 | path = Extension;
123 | sourceTree = "";
124 | };
125 | 98D573E42758A0E2001C2D8C = {
126 | isa = PBXGroup;
127 | children = (
128 | 98D573EF2758A0E2001C2D8C /* HostsX */,
129 | 98D573EE2758A0E2001C2D8C /* Products */,
130 | );
131 | sourceTree = "";
132 | };
133 | 98D573EE2758A0E2001C2D8C /* Products */ = {
134 | isa = PBXGroup;
135 | children = (
136 | 98D573ED2758A0E2001C2D8C /* HostsX.app */,
137 | );
138 | name = Products;
139 | sourceTree = "";
140 | };
141 | 98D573EF2758A0E2001C2D8C /* HostsX */ = {
142 | isa = PBXGroup;
143 | children = (
144 | 98A019F2276094690061CE7D /* Extension */,
145 | 98A019EF275F3E890061CE7D /* Service */,
146 | 98F449AB275DAF5B00BFBE87 /* Component */,
147 | 98EFEEB2275A61AB00B996C0 /* Model */,
148 | 98F449A32759FAE000BFBE87 /* Module */,
149 | 98D574042758D349001C2D8C /* Info.plist */,
150 | 98D573F02758A0E2001C2D8C /* AppDelegate.swift */,
151 | 98706F6B276C5EC000037F77 /* AppMenu.swift */,
152 | 98F449A9275DA5E400BFBE87 /* Constant.swift */,
153 | 98706F73276C78EF00037F77 /* Localizable.strings */,
154 | 98706F69276C37B500037F77 /* Menu.xib */,
155 | 98D573F62758A0E4001C2D8C /* Remote.storyboard */,
156 | 983E5366277367FD00CE84DE /* Help.storyboard */,
157 | 98D573F42758A0E4001C2D8C /* Assets.xcassets */,
158 | 98D573F92758A0E4001C2D8C /* HostsX.entitlements */,
159 | );
160 | path = HostsX;
161 | sourceTree = "";
162 | };
163 | 98EFEEB2275A61AB00B996C0 /* Model */ = {
164 | isa = PBXGroup;
165 | children = (
166 | 98EFEEB3275A61BA00B996C0 /* Hosts.swift */,
167 | 98706F7A2770684B00037F77 /* HostsError.swift */,
168 | );
169 | path = Model;
170 | sourceTree = "";
171 | };
172 | 98F449A32759FAE000BFBE87 /* Module */ = {
173 | isa = PBXGroup;
174 | children = (
175 | 983E5361277367AD00CE84DE /* Help */,
176 | 98F449A62759FB1B00BFBE87 /* Remote */,
177 | );
178 | path = Module;
179 | sourceTree = "";
180 | };
181 | 98F449A62759FB1B00BFBE87 /* Remote */ = {
182 | isa = PBXGroup;
183 | children = (
184 | 98D573F22758A0E2001C2D8C /* RemoteConfigController.swift */,
185 | 98A019E5275DFA220061CE7D /* HostsConfigController.swift */,
186 | );
187 | path = Remote;
188 | sourceTree = "";
189 | };
190 | 98F449AB275DAF5B00BFBE87 /* Component */ = {
191 | isa = PBXGroup;
192 | children = (
193 | 98D574002758C093001C2D8C /* HostsURLCell.swift */,
194 | 98D929CE2774211F00CC4E42 /* HostsView.swift */,
195 | 98A019E0275DFA220061CE7D /* Dialog */,
196 | );
197 | path = Component;
198 | sourceTree = "";
199 | };
200 | /* End PBXGroup section */
201 |
202 | /* Begin PBXNativeTarget section */
203 | 98D573EC2758A0E2001C2D8C /* HostsX */ = {
204 | isa = PBXNativeTarget;
205 | buildConfigurationList = 98D573FC2758A0E4001C2D8C /* Build configuration list for PBXNativeTarget "HostsX" */;
206 | buildPhases = (
207 | 98D573E92758A0E2001C2D8C /* Sources */,
208 | 98D573EA2758A0E2001C2D8C /* Frameworks */,
209 | 98D573EB2758A0E2001C2D8C /* Resources */,
210 | );
211 | buildRules = (
212 | );
213 | dependencies = (
214 | );
215 | name = HostsX;
216 | packageProductDependencies = (
217 | );
218 | productName = HostsX;
219 | productReference = 98D573ED2758A0E2001C2D8C /* HostsX.app */;
220 | productType = "com.apple.product-type.application";
221 | };
222 | /* End PBXNativeTarget section */
223 |
224 | /* Begin PBXProject section */
225 | 98D573E52758A0E2001C2D8C /* Project object */ = {
226 | isa = PBXProject;
227 | attributes = {
228 | BuildIndependentTargetsInParallel = 1;
229 | LastSwiftUpdateCheck = 1310;
230 | LastUpgradeCheck = 1320;
231 | TargetAttributes = {
232 | 98D573EC2758A0E2001C2D8C = {
233 | CreatedOnToolsVersion = 13.1;
234 | };
235 | };
236 | };
237 | buildConfigurationList = 98D573E82758A0E2001C2D8C /* Build configuration list for PBXProject "HostsX" */;
238 | compatibilityVersion = "Xcode 13.0";
239 | developmentRegion = en;
240 | hasScannedForEncodings = 0;
241 | knownRegions = (
242 | en,
243 | Base,
244 | "zh-Hans",
245 | );
246 | mainGroup = 98D573E42758A0E2001C2D8C;
247 | packageReferences = (
248 | 98FBFABF2806A51F0027D74C /* XCRemoteSwiftPackageReference "Sparkle" */,
249 | );
250 | productRefGroup = 98D573EE2758A0E2001C2D8C /* Products */;
251 | projectDirPath = "";
252 | projectRoot = "";
253 | targets = (
254 | 98D573EC2758A0E2001C2D8C /* HostsX */,
255 | );
256 | };
257 | /* End PBXProject section */
258 |
259 | /* Begin PBXResourcesBuildPhase section */
260 | 98D573EB2758A0E2001C2D8C /* Resources */ = {
261 | isa = PBXResourcesBuildPhase;
262 | buildActionMask = 2147483647;
263 | files = (
264 | 98A019EA275DFA220061CE7D /* InfoDialogController.xib in Resources */,
265 | 98D573F52758A0E4001C2D8C /* Assets.xcassets in Resources */,
266 | 98706F71276C78EF00037F77 /* Localizable.strings in Resources */,
267 | 98A019E8275DFA220061CE7D /* ConfirmDialogController.xib in Resources */,
268 | 983E5367277367FD00CE84DE /* Help.storyboard in Resources */,
269 | 98D573F82758A0E4001C2D8C /* Remote.storyboard in Resources */,
270 | 98706F6A276C37B500037F77 /* Menu.xib in Resources */,
271 | );
272 | runOnlyForDeploymentPostprocessing = 0;
273 | };
274 | /* End PBXResourcesBuildPhase section */
275 |
276 | /* Begin PBXSourcesBuildPhase section */
277 | 98D573E92758A0E2001C2D8C /* Sources */ = {
278 | isa = PBXSourcesBuildPhase;
279 | buildActionMask = 2147483647;
280 | files = (
281 | 98D573F32758A0E2001C2D8C /* RemoteConfigController.swift in Sources */,
282 | 98706F7B2770684B00037F77 /* HostsError.swift in Sources */,
283 | 98D929CF2774211F00CC4E42 /* HostsView.swift in Sources */,
284 | 98A019F1275F3EA20061CE7D /* RemoteSource.swift in Sources */,
285 | 98A019EE275E01730061CE7D /* Dialog.swift in Sources */,
286 | 98526DC72769C44800D84F87 /* AppKit+.swift in Sources */,
287 | 98D574032758CECF001C2D8C /* Network.swift in Sources */,
288 | 98F449AA275DA5E400BFBE87 /* Constant.swift in Sources */,
289 | 98A019EC275DFA220061CE7D /* InfoDialogController.swift in Sources */,
290 | 98A019EB275DFA220061CE7D /* HostsConfigController.swift in Sources */,
291 | 983E53692773696500CE84DE /* AboutViewController.swift in Sources */,
292 | 9828C8372772C3A800CC87BC /* NotificationHelper.swift in Sources */,
293 | 98A019E9275DFA220061CE7D /* ConfirmDialogController.swift in Sources */,
294 | 98706F7927705DC700037F77 /* FileHelper.swift in Sources */,
295 | 98EFEEB4275A61BA00B996C0 /* Hosts.swift in Sources */,
296 | 98706F6C276C5EC000037F77 /* AppMenu.swift in Sources */,
297 | 98706F76276C7C3600037F77 /* Localization.swift in Sources */,
298 | 98526DC52769C40E00D84F87 /* Foundition+.swift in Sources */,
299 | 98D573F12758A0E2001C2D8C /* AppDelegate.swift in Sources */,
300 | 98D574012758C093001C2D8C /* HostsURLCell.swift in Sources */,
301 | );
302 | runOnlyForDeploymentPostprocessing = 0;
303 | };
304 | /* End PBXSourcesBuildPhase section */
305 |
306 | /* Begin PBXVariantGroup section */
307 | 98706F73276C78EF00037F77 /* Localizable.strings */ = {
308 | isa = PBXVariantGroup;
309 | children = (
310 | 98706F72276C78EF00037F77 /* en */,
311 | 98706F74276C791800037F77 /* zh-Hans */,
312 | );
313 | name = Localizable.strings;
314 | sourceTree = "";
315 | };
316 | 98D573F62758A0E4001C2D8C /* Remote.storyboard */ = {
317 | isa = PBXVariantGroup;
318 | children = (
319 | 98D573F72758A0E4001C2D8C /* Base */,
320 | );
321 | name = Remote.storyboard;
322 | sourceTree = "";
323 | };
324 | /* End PBXVariantGroup section */
325 |
326 | /* Begin XCBuildConfiguration section */
327 | 98D573FA2758A0E4001C2D8C /* Debug */ = {
328 | isa = XCBuildConfiguration;
329 | buildSettings = {
330 | ALWAYS_SEARCH_USER_PATHS = NO;
331 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
332 | CLANG_ANALYZER_NONNULL = YES;
333 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
334 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
335 | CLANG_CXX_LIBRARY = "libc++";
336 | CLANG_ENABLE_MODULES = YES;
337 | CLANG_ENABLE_OBJC_ARC = YES;
338 | CLANG_ENABLE_OBJC_WEAK = YES;
339 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
340 | CLANG_WARN_BOOL_CONVERSION = YES;
341 | CLANG_WARN_COMMA = YES;
342 | CLANG_WARN_CONSTANT_CONVERSION = YES;
343 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
344 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
345 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
346 | CLANG_WARN_EMPTY_BODY = YES;
347 | CLANG_WARN_ENUM_CONVERSION = YES;
348 | CLANG_WARN_INFINITE_RECURSION = YES;
349 | CLANG_WARN_INT_CONVERSION = YES;
350 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
351 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
352 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
353 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
354 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
355 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
356 | CLANG_WARN_STRICT_PROTOTYPES = YES;
357 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
358 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
359 | CLANG_WARN_UNREACHABLE_CODE = YES;
360 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
361 | COPY_PHASE_STRIP = NO;
362 | DEBUG_INFORMATION_FORMAT = dwarf;
363 | ENABLE_STRICT_OBJC_MSGSEND = YES;
364 | ENABLE_TESTABILITY = YES;
365 | GCC_C_LANGUAGE_STANDARD = gnu11;
366 | GCC_DYNAMIC_NO_PIC = NO;
367 | GCC_NO_COMMON_BLOCKS = YES;
368 | GCC_OPTIMIZATION_LEVEL = 0;
369 | GCC_PREPROCESSOR_DEFINITIONS = (
370 | "DEBUG=1",
371 | "$(inherited)",
372 | );
373 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
374 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
375 | GCC_WARN_UNDECLARED_SELECTOR = YES;
376 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
377 | GCC_WARN_UNUSED_FUNCTION = YES;
378 | GCC_WARN_UNUSED_VARIABLE = YES;
379 | MACOSX_DEPLOYMENT_TARGET = 10.12;
380 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
381 | MTL_FAST_MATH = YES;
382 | ONLY_ACTIVE_ARCH = YES;
383 | SDKROOT = macosx;
384 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
385 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
386 | };
387 | name = Debug;
388 | };
389 | 98D573FB2758A0E4001C2D8C /* Release */ = {
390 | isa = XCBuildConfiguration;
391 | buildSettings = {
392 | ALWAYS_SEARCH_USER_PATHS = NO;
393 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
394 | CLANG_ANALYZER_NONNULL = YES;
395 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
396 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
397 | CLANG_CXX_LIBRARY = "libc++";
398 | CLANG_ENABLE_MODULES = YES;
399 | CLANG_ENABLE_OBJC_ARC = YES;
400 | CLANG_ENABLE_OBJC_WEAK = YES;
401 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
402 | CLANG_WARN_BOOL_CONVERSION = YES;
403 | CLANG_WARN_COMMA = YES;
404 | CLANG_WARN_CONSTANT_CONVERSION = YES;
405 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
406 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
407 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
408 | CLANG_WARN_EMPTY_BODY = YES;
409 | CLANG_WARN_ENUM_CONVERSION = YES;
410 | CLANG_WARN_INFINITE_RECURSION = YES;
411 | CLANG_WARN_INT_CONVERSION = YES;
412 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
413 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
414 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
415 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
416 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
417 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
418 | CLANG_WARN_STRICT_PROTOTYPES = YES;
419 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
420 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
421 | CLANG_WARN_UNREACHABLE_CODE = YES;
422 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
423 | COPY_PHASE_STRIP = NO;
424 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
425 | ENABLE_NS_ASSERTIONS = NO;
426 | ENABLE_STRICT_OBJC_MSGSEND = YES;
427 | GCC_C_LANGUAGE_STANDARD = gnu11;
428 | GCC_NO_COMMON_BLOCKS = YES;
429 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
430 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
431 | GCC_WARN_UNDECLARED_SELECTOR = YES;
432 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
433 | GCC_WARN_UNUSED_FUNCTION = YES;
434 | GCC_WARN_UNUSED_VARIABLE = YES;
435 | MACOSX_DEPLOYMENT_TARGET = 10.12;
436 | MTL_ENABLE_DEBUG_INFO = NO;
437 | MTL_FAST_MATH = YES;
438 | SDKROOT = macosx;
439 | SWIFT_COMPILATION_MODE = wholemodule;
440 | SWIFT_OPTIMIZATION_LEVEL = "-O";
441 | };
442 | name = Release;
443 | };
444 | 98D573FD2758A0E4001C2D8C /* Debug */ = {
445 | isa = XCBuildConfiguration;
446 | buildSettings = {
447 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
448 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
449 | CODE_SIGN_ENTITLEMENTS = HostsX/HostsX.entitlements;
450 | CODE_SIGN_IDENTITY = "-";
451 | CODE_SIGN_STYLE = Manual;
452 | COMBINE_HIDPI_IMAGES = YES;
453 | CURRENT_PROJECT_VERSION = 1;
454 | DEVELOPMENT_TEAM = "";
455 | GENERATE_INFOPLIST_FILE = YES;
456 | INFOPLIST_FILE = HostsX/Info.plist;
457 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
458 | INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2016 ZzzM. All rights reserved.";
459 | INFOPLIST_KEY_NSMainNibFile = Menu;
460 | INFOPLIST_KEY_NSPrincipalClass = NSApplication;
461 | LD_RUNPATH_SEARCH_PATHS = (
462 | "$(inherited)",
463 | "@executable_path/../Frameworks",
464 | );
465 | MACOSX_DEPLOYMENT_TARGET = 10.12;
466 | MARKETING_VERSION = 2.8.1;
467 | PRODUCT_BUNDLE_IDENTIFIER = com.alpha.hostsx;
468 | PRODUCT_NAME = "$(TARGET_NAME)";
469 | PROVISIONING_PROFILE_SPECIFIER = "";
470 | SWIFT_EMIT_LOC_STRINGS = YES;
471 | SWIFT_VERSION = 5.0;
472 | };
473 | name = Debug;
474 | };
475 | 98D573FE2758A0E4001C2D8C /* Release */ = {
476 | isa = XCBuildConfiguration;
477 | buildSettings = {
478 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
479 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
480 | CODE_SIGN_ENTITLEMENTS = HostsX/HostsX.entitlements;
481 | CODE_SIGN_IDENTITY = "-";
482 | CODE_SIGN_STYLE = Manual;
483 | COMBINE_HIDPI_IMAGES = YES;
484 | CURRENT_PROJECT_VERSION = 1;
485 | DEVELOPMENT_TEAM = "";
486 | GENERATE_INFOPLIST_FILE = YES;
487 | INFOPLIST_FILE = HostsX/Info.plist;
488 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
489 | INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2016 ZzzM. All rights reserved.";
490 | INFOPLIST_KEY_NSMainNibFile = Menu;
491 | INFOPLIST_KEY_NSPrincipalClass = NSApplication;
492 | LD_RUNPATH_SEARCH_PATHS = (
493 | "$(inherited)",
494 | "@executable_path/../Frameworks",
495 | );
496 | MACOSX_DEPLOYMENT_TARGET = 10.12;
497 | MARKETING_VERSION = 2.8.1;
498 | PRODUCT_BUNDLE_IDENTIFIER = com.alpha.hostsx;
499 | PRODUCT_NAME = "$(TARGET_NAME)";
500 | PROVISIONING_PROFILE_SPECIFIER = "";
501 | SWIFT_EMIT_LOC_STRINGS = YES;
502 | SWIFT_VERSION = 5.0;
503 | };
504 | name = Release;
505 | };
506 | /* End XCBuildConfiguration section */
507 |
508 | /* Begin XCConfigurationList section */
509 | 98D573E82758A0E2001C2D8C /* Build configuration list for PBXProject "HostsX" */ = {
510 | isa = XCConfigurationList;
511 | buildConfigurations = (
512 | 98D573FA2758A0E4001C2D8C /* Debug */,
513 | 98D573FB2758A0E4001C2D8C /* Release */,
514 | );
515 | defaultConfigurationIsVisible = 0;
516 | defaultConfigurationName = Release;
517 | };
518 | 98D573FC2758A0E4001C2D8C /* Build configuration list for PBXNativeTarget "HostsX" */ = {
519 | isa = XCConfigurationList;
520 | buildConfigurations = (
521 | 98D573FD2758A0E4001C2D8C /* Debug */,
522 | 98D573FE2758A0E4001C2D8C /* Release */,
523 | );
524 | defaultConfigurationIsVisible = 0;
525 | defaultConfigurationName = Release;
526 | };
527 | /* End XCConfigurationList section */
528 |
529 | /* Begin XCRemoteSwiftPackageReference section */
530 | 98FBFABF2806A51F0027D74C /* XCRemoteSwiftPackageReference "Sparkle" */ = {
531 | isa = XCRemoteSwiftPackageReference;
532 | repositoryURL = "https://github.com/sparkle-project/Sparkle";
533 | requirement = {
534 | kind = exactVersion;
535 | version = 2.1.0;
536 | };
537 | };
538 | /* End XCRemoteSwiftPackageReference section */
539 | };
540 | rootObject = 98D573E52758A0E2001C2D8C /* Project object */;
541 | }
542 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU AFFERO GENERAL PUBLIC LICENSE
2 | Version 3, 19 November 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU Affero General Public License is a free, copyleft license for
11 | software and other kinds of works, specifically designed to ensure
12 | cooperation with the community in the case of network server software.
13 |
14 | The licenses for most software and other practical works are designed
15 | to take away your freedom to share and change the works. By contrast,
16 | our General Public Licenses are intended to guarantee your freedom to
17 | share and change all versions of a program--to make sure it remains free
18 | software for all its users.
19 |
20 | When we speak of free software, we are referring to freedom, not
21 | price. Our General Public Licenses are designed to make sure that you
22 | have the freedom to distribute copies of free software (and charge for
23 | them if you wish), that you receive source code or can get it if you
24 | want it, that you can change the software or use pieces of it in new
25 | free programs, and that you know you can do these things.
26 |
27 | Developers that use our General Public Licenses protect your rights
28 | with two steps: (1) assert copyright on the software, and (2) offer
29 | you this License which gives you legal permission to copy, distribute
30 | and/or modify the software.
31 |
32 | A secondary benefit of defending all users' freedom is that
33 | improvements made in alternate versions of the program, if they
34 | receive widespread use, become available for other developers to
35 | incorporate. Many developers of free software are heartened and
36 | encouraged by the resulting cooperation. However, in the case of
37 | software used on network servers, this result may fail to come about.
38 | The GNU General Public License permits making a modified version and
39 | letting the public access it on a server without ever releasing its
40 | source code to the public.
41 |
42 | The GNU Affero General Public License is designed specifically to
43 | ensure that, in such cases, the modified source code becomes available
44 | to the community. It requires the operator of a network server to
45 | provide the source code of the modified version running there to the
46 | users of that server. Therefore, public use of a modified version, on
47 | a publicly accessible server, gives the public access to the source
48 | code of the modified version.
49 |
50 | An older license, called the Affero General Public License and
51 | published by Affero, was designed to accomplish similar goals. This is
52 | a different license, not a version of the Affero GPL, but Affero has
53 | released a new version of the Affero GPL which permits relicensing under
54 | this license.
55 |
56 | The precise terms and conditions for copying, distribution and
57 | modification follow.
58 |
59 | TERMS AND CONDITIONS
60 |
61 | 0. Definitions.
62 |
63 | "This License" refers to version 3 of the GNU Affero General Public License.
64 |
65 | "Copyright" also means copyright-like laws that apply to other kinds of
66 | works, such as semiconductor masks.
67 |
68 | "The Program" refers to any copyrightable work licensed under this
69 | License. Each licensee is addressed as "you". "Licensees" and
70 | "recipients" may be individuals or organizations.
71 |
72 | To "modify" a work means to copy from or adapt all or part of the work
73 | in a fashion requiring copyright permission, other than the making of an
74 | exact copy. The resulting work is called a "modified version" of the
75 | earlier work or a work "based on" the earlier work.
76 |
77 | A "covered work" means either the unmodified Program or a work based
78 | on the Program.
79 |
80 | To "propagate" a work means to do anything with it that, without
81 | permission, would make you directly or secondarily liable for
82 | infringement under applicable copyright law, except executing it on a
83 | computer or modifying a private copy. Propagation includes copying,
84 | distribution (with or without modification), making available to the
85 | public, and in some countries other activities as well.
86 |
87 | To "convey" a work means any kind of propagation that enables other
88 | parties to make or receive copies. Mere interaction with a user through
89 | a computer network, with no transfer of a copy, is not conveying.
90 |
91 | An interactive user interface displays "Appropriate Legal Notices"
92 | to the extent that it includes a convenient and prominently visible
93 | feature that (1) displays an appropriate copyright notice, and (2)
94 | tells the user that there is no warranty for the work (except to the
95 | extent that warranties are provided), that licensees may convey the
96 | work under this License, and how to view a copy of this License. If
97 | the interface presents a list of user commands or options, such as a
98 | menu, a prominent item in the list meets this criterion.
99 |
100 | 1. Source Code.
101 |
102 | The "source code" for a work means the preferred form of the work
103 | for making modifications to it. "Object code" means any non-source
104 | form of a work.
105 |
106 | A "Standard Interface" means an interface that either is an official
107 | standard defined by a recognized standards body, or, in the case of
108 | interfaces specified for a particular programming language, one that
109 | is widely used among developers working in that language.
110 |
111 | The "System Libraries" of an executable work include anything, other
112 | than the work as a whole, that (a) is included in the normal form of
113 | packaging a Major Component, but which is not part of that Major
114 | Component, and (b) serves only to enable use of the work with that
115 | Major Component, or to implement a Standard Interface for which an
116 | implementation is available to the public in source code form. A
117 | "Major Component", in this context, means a major essential component
118 | (kernel, window system, and so on) of the specific operating system
119 | (if any) on which the executable work runs, or a compiler used to
120 | produce the work, or an object code interpreter used to run it.
121 |
122 | The "Corresponding Source" for a work in object code form means all
123 | the source code needed to generate, install, and (for an executable
124 | work) run the object code and to modify the work, including scripts to
125 | control those activities. However, it does not include the work's
126 | System Libraries, or general-purpose tools or generally available free
127 | programs which are used unmodified in performing those activities but
128 | which are not part of the work. For example, Corresponding Source
129 | includes interface definition files associated with source files for
130 | the work, and the source code for shared libraries and dynamically
131 | linked subprograms that the work is specifically designed to require,
132 | such as by intimate data communication or control flow between those
133 | subprograms and other parts of the work.
134 |
135 | The Corresponding Source need not include anything that users
136 | can regenerate automatically from other parts of the Corresponding
137 | Source.
138 |
139 | The Corresponding Source for a work in source code form is that
140 | same work.
141 |
142 | 2. Basic Permissions.
143 |
144 | All rights granted under this License are granted for the term of
145 | copyright on the Program, and are irrevocable provided the stated
146 | conditions are met. This License explicitly affirms your unlimited
147 | permission to run the unmodified Program. The output from running a
148 | covered work is covered by this License only if the output, given its
149 | content, constitutes a covered work. This License acknowledges your
150 | rights of fair use or other equivalent, as provided by copyright law.
151 |
152 | You may make, run and propagate covered works that you do not
153 | convey, without conditions so long as your license otherwise remains
154 | in force. You may convey covered works to others for the sole purpose
155 | of having them make modifications exclusively for you, or provide you
156 | with facilities for running those works, provided that you comply with
157 | the terms of this License in conveying all material for which you do
158 | not control copyright. Those thus making or running the covered works
159 | for you must do so exclusively on your behalf, under your direction
160 | and control, on terms that prohibit them from making any copies of
161 | your copyrighted material outside their relationship with you.
162 |
163 | Conveying under any other circumstances is permitted solely under
164 | the conditions stated below. Sublicensing is not allowed; section 10
165 | makes it unnecessary.
166 |
167 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
168 |
169 | No covered work shall be deemed part of an effective technological
170 | measure under any applicable law fulfilling obligations under article
171 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
172 | similar laws prohibiting or restricting circumvention of such
173 | measures.
174 |
175 | When you convey a covered work, you waive any legal power to forbid
176 | circumvention of technological measures to the extent such circumvention
177 | is effected by exercising rights under this License with respect to
178 | the covered work, and you disclaim any intention to limit operation or
179 | modification of the work as a means of enforcing, against the work's
180 | users, your or third parties' legal rights to forbid circumvention of
181 | technological measures.
182 |
183 | 4. Conveying Verbatim Copies.
184 |
185 | You may convey verbatim copies of the Program's source code as you
186 | receive it, in any medium, provided that you conspicuously and
187 | appropriately publish on each copy an appropriate copyright notice;
188 | keep intact all notices stating that this License and any
189 | non-permissive terms added in accord with section 7 apply to the code;
190 | keep intact all notices of the absence of any warranty; and give all
191 | recipients a copy of this License along with the Program.
192 |
193 | You may charge any price or no price for each copy that you convey,
194 | and you may offer support or warranty protection for a fee.
195 |
196 | 5. Conveying Modified Source Versions.
197 |
198 | You may convey a work based on the Program, or the modifications to
199 | produce it from the Program, in the form of source code under the
200 | terms of section 4, provided that you also meet all of these conditions:
201 |
202 | a) The work must carry prominent notices stating that you modified
203 | it, and giving a relevant date.
204 |
205 | b) The work must carry prominent notices stating that it is
206 | released under this License and any conditions added under section
207 | 7. This requirement modifies the requirement in section 4 to
208 | "keep intact all notices".
209 |
210 | c) You must license the entire work, as a whole, under this
211 | License to anyone who comes into possession of a copy. This
212 | License will therefore apply, along with any applicable section 7
213 | additional terms, to the whole of the work, and all its parts,
214 | regardless of how they are packaged. This License gives no
215 | permission to license the work in any other way, but it does not
216 | invalidate such permission if you have separately received it.
217 |
218 | d) If the work has interactive user interfaces, each must display
219 | Appropriate Legal Notices; however, if the Program has interactive
220 | interfaces that do not display Appropriate Legal Notices, your
221 | work need not make them do so.
222 |
223 | A compilation of a covered work with other separate and independent
224 | works, which are not by their nature extensions of the covered work,
225 | and which are not combined with it such as to form a larger program,
226 | in or on a volume of a storage or distribution medium, is called an
227 | "aggregate" if the compilation and its resulting copyright are not
228 | used to limit the access or legal rights of the compilation's users
229 | beyond what the individual works permit. Inclusion of a covered work
230 | in an aggregate does not cause this License to apply to the other
231 | parts of the aggregate.
232 |
233 | 6. Conveying Non-Source Forms.
234 |
235 | You may convey a covered work in object code form under the terms
236 | of sections 4 and 5, provided that you also convey the
237 | machine-readable Corresponding Source under the terms of this License,
238 | in one of these ways:
239 |
240 | a) Convey the object code in, or embodied in, a physical product
241 | (including a physical distribution medium), accompanied by the
242 | Corresponding Source fixed on a durable physical medium
243 | customarily used for software interchange.
244 |
245 | b) Convey the object code in, or embodied in, a physical product
246 | (including a physical distribution medium), accompanied by a
247 | written offer, valid for at least three years and valid for as
248 | long as you offer spare parts or customer support for that product
249 | model, to give anyone who possesses the object code either (1) a
250 | copy of the Corresponding Source for all the software in the
251 | product that is covered by this License, on a durable physical
252 | medium customarily used for software interchange, for a price no
253 | more than your reasonable cost of physically performing this
254 | conveying of source, or (2) access to copy the
255 | Corresponding Source from a network server at no charge.
256 |
257 | c) Convey individual copies of the object code with a copy of the
258 | written offer to provide the Corresponding Source. This
259 | alternative is allowed only occasionally and noncommercially, and
260 | only if you received the object code with such an offer, in accord
261 | with subsection 6b.
262 |
263 | d) Convey the object code by offering access from a designated
264 | place (gratis or for a charge), and offer equivalent access to the
265 | Corresponding Source in the same way through the same place at no
266 | further charge. You need not require recipients to copy the
267 | Corresponding Source along with the object code. If the place to
268 | copy the object code is a network server, the Corresponding Source
269 | may be on a different server (operated by you or a third party)
270 | that supports equivalent copying facilities, provided you maintain
271 | clear directions next to the object code saying where to find the
272 | Corresponding Source. Regardless of what server hosts the
273 | Corresponding Source, you remain obligated to ensure that it is
274 | available for as long as needed to satisfy these requirements.
275 |
276 | e) Convey the object code using peer-to-peer transmission, provided
277 | you inform other peers where the object code and Corresponding
278 | Source of the work are being offered to the general public at no
279 | charge under subsection 6d.
280 |
281 | A separable portion of the object code, whose source code is excluded
282 | from the Corresponding Source as a System Library, need not be
283 | included in conveying the object code work.
284 |
285 | A "User Product" is either (1) a "consumer product", which means any
286 | tangible personal property which is normally used for personal, family,
287 | or household purposes, or (2) anything designed or sold for incorporation
288 | into a dwelling. In determining whether a product is a consumer product,
289 | doubtful cases shall be resolved in favor of coverage. For a particular
290 | product received by a particular user, "normally used" refers to a
291 | typical or common use of that class of product, regardless of the status
292 | of the particular user or of the way in which the particular user
293 | actually uses, or expects or is expected to use, the product. A product
294 | is a consumer product regardless of whether the product has substantial
295 | commercial, industrial or non-consumer uses, unless such uses represent
296 | the only significant mode of use of the product.
297 |
298 | "Installation Information" for a User Product means any methods,
299 | procedures, authorization keys, or other information required to install
300 | and execute modified versions of a covered work in that User Product from
301 | a modified version of its Corresponding Source. The information must
302 | suffice to ensure that the continued functioning of the modified object
303 | code is in no case prevented or interfered with solely because
304 | modification has been made.
305 |
306 | If you convey an object code work under this section in, or with, or
307 | specifically for use in, a User Product, and the conveying occurs as
308 | part of a transaction in which the right of possession and use of the
309 | User Product is transferred to the recipient in perpetuity or for a
310 | fixed term (regardless of how the transaction is characterized), the
311 | Corresponding Source conveyed under this section must be accompanied
312 | by the Installation Information. But this requirement does not apply
313 | if neither you nor any third party retains the ability to install
314 | modified object code on the User Product (for example, the work has
315 | been installed in ROM).
316 |
317 | The requirement to provide Installation Information does not include a
318 | requirement to continue to provide support service, warranty, or updates
319 | for a work that has been modified or installed by the recipient, or for
320 | the User Product in which it has been modified or installed. Access to a
321 | network may be denied when the modification itself materially and
322 | adversely affects the operation of the network or violates the rules and
323 | protocols for communication across the network.
324 |
325 | Corresponding Source conveyed, and Installation Information provided,
326 | in accord with this section must be in a format that is publicly
327 | documented (and with an implementation available to the public in
328 | source code form), and must require no special password or key for
329 | unpacking, reading or copying.
330 |
331 | 7. Additional Terms.
332 |
333 | "Additional permissions" are terms that supplement the terms of this
334 | License by making exceptions from one or more of its conditions.
335 | Additional permissions that are applicable to the entire Program shall
336 | be treated as though they were included in this License, to the extent
337 | that they are valid under applicable law. If additional permissions
338 | apply only to part of the Program, that part may be used separately
339 | under those permissions, but the entire Program remains governed by
340 | this License without regard to the additional permissions.
341 |
342 | When you convey a copy of a covered work, you may at your option
343 | remove any additional permissions from that copy, or from any part of
344 | it. (Additional permissions may be written to require their own
345 | removal in certain cases when you modify the work.) You may place
346 | additional permissions on material, added by you to a covered work,
347 | for which you have or can give appropriate copyright permission.
348 |
349 | Notwithstanding any other provision of this License, for material you
350 | add to a covered work, you may (if authorized by the copyright holders of
351 | that material) supplement the terms of this License with terms:
352 |
353 | a) Disclaiming warranty or limiting liability differently from the
354 | terms of sections 15 and 16 of this License; or
355 |
356 | b) Requiring preservation of specified reasonable legal notices or
357 | author attributions in that material or in the Appropriate Legal
358 | Notices displayed by works containing it; or
359 |
360 | c) Prohibiting misrepresentation of the origin of that material, or
361 | requiring that modified versions of such material be marked in
362 | reasonable ways as different from the original version; or
363 |
364 | d) Limiting the use for publicity purposes of names of licensors or
365 | authors of the material; or
366 |
367 | e) Declining to grant rights under trademark law for use of some
368 | trade names, trademarks, or service marks; or
369 |
370 | f) Requiring indemnification of licensors and authors of that
371 | material by anyone who conveys the material (or modified versions of
372 | it) with contractual assumptions of liability to the recipient, for
373 | any liability that these contractual assumptions directly impose on
374 | those licensors and authors.
375 |
376 | All other non-permissive additional terms are considered "further
377 | restrictions" within the meaning of section 10. If the Program as you
378 | received it, or any part of it, contains a notice stating that it is
379 | governed by this License along with a term that is a further
380 | restriction, you may remove that term. If a license document contains
381 | a further restriction but permits relicensing or conveying under this
382 | License, you may add to a covered work material governed by the terms
383 | of that license document, provided that the further restriction does
384 | not survive such relicensing or conveying.
385 |
386 | If you add terms to a covered work in accord with this section, you
387 | must place, in the relevant source files, a statement of the
388 | additional terms that apply to those files, or a notice indicating
389 | where to find the applicable terms.
390 |
391 | Additional terms, permissive or non-permissive, may be stated in the
392 | form of a separately written license, or stated as exceptions;
393 | the above requirements apply either way.
394 |
395 | 8. Termination.
396 |
397 | You may not propagate or modify a covered work except as expressly
398 | provided under this License. Any attempt otherwise to propagate or
399 | modify it is void, and will automatically terminate your rights under
400 | this License (including any patent licenses granted under the third
401 | paragraph of section 11).
402 |
403 | However, if you cease all violation of this License, then your
404 | license from a particular copyright holder is reinstated (a)
405 | provisionally, unless and until the copyright holder explicitly and
406 | finally terminates your license, and (b) permanently, if the copyright
407 | holder fails to notify you of the violation by some reasonable means
408 | prior to 60 days after the cessation.
409 |
410 | Moreover, your license from a particular copyright holder is
411 | reinstated permanently if the copyright holder notifies you of the
412 | violation by some reasonable means, this is the first time you have
413 | received notice of violation of this License (for any work) from that
414 | copyright holder, and you cure the violation prior to 30 days after
415 | your receipt of the notice.
416 |
417 | Termination of your rights under this section does not terminate the
418 | licenses of parties who have received copies or rights from you under
419 | this License. If your rights have been terminated and not permanently
420 | reinstated, you do not qualify to receive new licenses for the same
421 | material under section 10.
422 |
423 | 9. Acceptance Not Required for Having Copies.
424 |
425 | You are not required to accept this License in order to receive or
426 | run a copy of the Program. Ancillary propagation of a covered work
427 | occurring solely as a consequence of using peer-to-peer transmission
428 | to receive a copy likewise does not require acceptance. However,
429 | nothing other than this License grants you permission to propagate or
430 | modify any covered work. These actions infringe copyright if you do
431 | not accept this License. Therefore, by modifying or propagating a
432 | covered work, you indicate your acceptance of this License to do so.
433 |
434 | 10. Automatic Licensing of Downstream Recipients.
435 |
436 | Each time you convey a covered work, the recipient automatically
437 | receives a license from the original licensors, to run, modify and
438 | propagate that work, subject to this License. You are not responsible
439 | for enforcing compliance by third parties with this License.
440 |
441 | An "entity transaction" is a transaction transferring control of an
442 | organization, or substantially all assets of one, or subdividing an
443 | organization, or merging organizations. If propagation of a covered
444 | work results from an entity transaction, each party to that
445 | transaction who receives a copy of the work also receives whatever
446 | licenses to the work the party's predecessor in interest had or could
447 | give under the previous paragraph, plus a right to possession of the
448 | Corresponding Source of the work from the predecessor in interest, if
449 | the predecessor has it or can get it with reasonable efforts.
450 |
451 | You may not impose any further restrictions on the exercise of the
452 | rights granted or affirmed under this License. For example, you may
453 | not impose a license fee, royalty, or other charge for exercise of
454 | rights granted under this License, and you may not initiate litigation
455 | (including a cross-claim or counterclaim in a lawsuit) alleging that
456 | any patent claim is infringed by making, using, selling, offering for
457 | sale, or importing the Program or any portion of it.
458 |
459 | 11. Patents.
460 |
461 | A "contributor" is a copyright holder who authorizes use under this
462 | License of the Program or a work on which the Program is based. The
463 | work thus licensed is called the contributor's "contributor version".
464 |
465 | A contributor's "essential patent claims" are all patent claims
466 | owned or controlled by the contributor, whether already acquired or
467 | hereafter acquired, that would be infringed by some manner, permitted
468 | by this License, of making, using, or selling its contributor version,
469 | but do not include claims that would be infringed only as a
470 | consequence of further modification of the contributor version. For
471 | purposes of this definition, "control" includes the right to grant
472 | patent sublicenses in a manner consistent with the requirements of
473 | this License.
474 |
475 | Each contributor grants you a non-exclusive, worldwide, royalty-free
476 | patent license under the contributor's essential patent claims, to
477 | make, use, sell, offer for sale, import and otherwise run, modify and
478 | propagate the contents of its contributor version.
479 |
480 | In the following three paragraphs, a "patent license" is any express
481 | agreement or commitment, however denominated, not to enforce a patent
482 | (such as an express permission to practice a patent or covenant not to
483 | sue for patent infringement). To "grant" such a patent license to a
484 | party means to make such an agreement or commitment not to enforce a
485 | patent against the party.
486 |
487 | If you convey a covered work, knowingly relying on a patent license,
488 | and the Corresponding Source of the work is not available for anyone
489 | to copy, free of charge and under the terms of this License, through a
490 | publicly available network server or other readily accessible means,
491 | then you must either (1) cause the Corresponding Source to be so
492 | available, or (2) arrange to deprive yourself of the benefit of the
493 | patent license for this particular work, or (3) arrange, in a manner
494 | consistent with the requirements of this License, to extend the patent
495 | license to downstream recipients. "Knowingly relying" means you have
496 | actual knowledge that, but for the patent license, your conveying the
497 | covered work in a country, or your recipient's use of the covered work
498 | in a country, would infringe one or more identifiable patents in that
499 | country that you have reason to believe are valid.
500 |
501 | If, pursuant to or in connection with a single transaction or
502 | arrangement, you convey, or propagate by procuring conveyance of, a
503 | covered work, and grant a patent license to some of the parties
504 | receiving the covered work authorizing them to use, propagate, modify
505 | or convey a specific copy of the covered work, then the patent license
506 | you grant is automatically extended to all recipients of the covered
507 | work and works based on it.
508 |
509 | A patent license is "discriminatory" if it does not include within
510 | the scope of its coverage, prohibits the exercise of, or is
511 | conditioned on the non-exercise of one or more of the rights that are
512 | specifically granted under this License. You may not convey a covered
513 | work if you are a party to an arrangement with a third party that is
514 | in the business of distributing software, under which you make payment
515 | to the third party based on the extent of your activity of conveying
516 | the work, and under which the third party grants, to any of the
517 | parties who would receive the covered work from you, a discriminatory
518 | patent license (a) in connection with copies of the covered work
519 | conveyed by you (or copies made from those copies), or (b) primarily
520 | for and in connection with specific products or compilations that
521 | contain the covered work, unless you entered into that arrangement,
522 | or that patent license was granted, prior to 28 March 2007.
523 |
524 | Nothing in this License shall be construed as excluding or limiting
525 | any implied license or other defenses to infringement that may
526 | otherwise be available to you under applicable patent law.
527 |
528 | 12. No Surrender of Others' Freedom.
529 |
530 | If conditions are imposed on you (whether by court order, agreement or
531 | otherwise) that contradict the conditions of this License, they do not
532 | excuse you from the conditions of this License. If you cannot convey a
533 | covered work so as to satisfy simultaneously your obligations under this
534 | License and any other pertinent obligations, then as a consequence you may
535 | not convey it at all. For example, if you agree to terms that obligate you
536 | to collect a royalty for further conveying from those to whom you convey
537 | the Program, the only way you could satisfy both those terms and this
538 | License would be to refrain entirely from conveying the Program.
539 |
540 | 13. Remote Network Interaction; Use with the GNU General Public License.
541 |
542 | Notwithstanding any other provision of this License, if you modify the
543 | Program, your modified version must prominently offer all users
544 | interacting with it remotely through a computer network (if your version
545 | supports such interaction) an opportunity to receive the Corresponding
546 | Source of your version by providing access to the Corresponding Source
547 | from a network server at no charge, through some standard or customary
548 | means of facilitating copying of software. This Corresponding Source
549 | shall include the Corresponding Source for any work covered by version 3
550 | of the GNU General Public License that is incorporated pursuant to the
551 | following paragraph.
552 |
553 | Notwithstanding any other provision of this License, you have
554 | permission to link or combine any covered work with a work licensed
555 | under version 3 of the GNU General Public License into a single
556 | combined work, and to convey the resulting work. The terms of this
557 | License will continue to apply to the part which is the covered work,
558 | but the work with which it is combined will remain governed by version
559 | 3 of the GNU General Public License.
560 |
561 | 14. Revised Versions of this License.
562 |
563 | The Free Software Foundation may publish revised and/or new versions of
564 | the GNU Affero General Public License from time to time. Such new versions
565 | will be similar in spirit to the present version, but may differ in detail to
566 | address new problems or concerns.
567 |
568 | Each version is given a distinguishing version number. If the
569 | Program specifies that a certain numbered version of the GNU Affero General
570 | Public License "or any later version" applies to it, you have the
571 | option of following the terms and conditions either of that numbered
572 | version or of any later version published by the Free Software
573 | Foundation. If the Program does not specify a version number of the
574 | GNU Affero General Public License, you may choose any version ever published
575 | by the Free Software Foundation.
576 |
577 | If the Program specifies that a proxy can decide which future
578 | versions of the GNU Affero General Public License can be used, that proxy's
579 | public statement of acceptance of a version permanently authorizes you
580 | to choose that version for the Program.
581 |
582 | Later license versions may give you additional or different
583 | permissions. However, no additional obligations are imposed on any
584 | author or copyright holder as a result of your choosing to follow a
585 | later version.
586 |
587 | 15. Disclaimer of Warranty.
588 |
589 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
590 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
591 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
592 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
593 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
594 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
595 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
596 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
597 |
598 | 16. Limitation of Liability.
599 |
600 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
601 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
602 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
603 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
604 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
605 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
606 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
607 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
608 | SUCH DAMAGES.
609 |
610 | 17. Interpretation of Sections 15 and 16.
611 |
612 | If the disclaimer of warranty and limitation of liability provided
613 | above cannot be given local legal effect according to their terms,
614 | reviewing courts shall apply local law that most closely approximates
615 | an absolute waiver of all civil liability in connection with the
616 | Program, unless a warranty or assumption of liability accompanies a
617 | copy of the Program in return for a fee.
618 |
619 | END OF TERMS AND CONDITIONS
620 |
621 | How to Apply These Terms to Your New Programs
622 |
623 | If you develop a new program, and you want it to be of the greatest
624 | possible use to the public, the best way to achieve this is to make it
625 | free software which everyone can redistribute and change under these terms.
626 |
627 | To do so, attach the following notices to the program. It is safest
628 | to attach them to the start of each source file to most effectively
629 | state the exclusion of warranty; and each file should have at least
630 | the "copyright" line and a pointer to where the full notice is found.
631 |
632 |
633 | Copyright (C)
634 |
635 | This program is free software: you can redistribute it and/or modify
636 | it under the terms of the GNU Affero General Public License as published
637 | by the Free Software Foundation, either version 3 of the License, or
638 | (at your option) any later version.
639 |
640 | This program is distributed in the hope that it will be useful,
641 | but WITHOUT ANY WARRANTY; without even the implied warranty of
642 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
643 | GNU Affero General Public License for more details.
644 |
645 | You should have received a copy of the GNU Affero General Public License
646 | along with this program. If not, see .
647 |
648 | Also add information on how to contact you by electronic and paper mail.
649 |
650 | If your software can interact with users remotely through a computer
651 | network, you should also make sure that it provides a way for users to
652 | get its source. For example, if your program is a web application, its
653 | interface could display a "Source" link that leads users to an archive
654 | of the code. There are many ways you could offer source, and different
655 | solutions will be better for different programs; see section 13 for the
656 | specific requirements.
657 |
658 | You should also get your employer (if you work as a programmer) or school,
659 | if any, to sign a "copyright disclaimer" for the program, if necessary.
660 | For more information on this, and how to apply and follow the GNU AGPL, see
661 | .
662 |
--------------------------------------------------------------------------------
/HostsX/Base.lproj/Remote.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
98 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 |
336 |
337 |
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 |
346 |
347 |
348 |
349 |
350 |
351 |
352 |
353 |
354 |
355 |
356 |
357 |
358 |
359 |
360 |
361 |
362 |
363 |
364 |
365 |
--------------------------------------------------------------------------------