Files
ShadowsocksX-NG/ShadowsocksX-NG/PreferencesWindowController.swift

453 lines
17 KiB
Swift
Raw Normal View History

2016-06-09 00:30:08 +08:00
//
// PreferencesWindowController.swift
// ShadowsocksX-NG
//
// Created by on 16/6/6.
// Copyright © 2016 qiuyuzhou. All rights reserved.
//
import Cocoa
2017-03-23 23:19:42 +08:00
import RxCocoa
import RxSwift
2016-06-09 00:30:08 +08:00
class PreferencesWindowController: NSWindowController
, NSTableViewDataSource, NSTableViewDelegate {
@IBOutlet weak var profilesTableView: NSTableView!
@IBOutlet weak var profileBox: NSBox!
2017-01-11 16:44:58 +08:00
@IBOutlet weak var kcptunProfileBox: NSBox!
2016-06-09 00:30:08 +08:00
@IBOutlet weak var hostTextField: NSTextField!
@IBOutlet weak var portTextField: NSTextField!
2017-03-23 23:19:42 +08:00
@IBOutlet weak var kcptunPortTextField: NSTextField!
2016-06-09 00:30:08 +08:00
@IBOutlet weak var methodTextField: NSComboBox!
@IBOutlet weak var passwordTextField: NSTextField!
@IBOutlet weak var remarkTextField: NSTextField!
@IBOutlet weak var otaCheckBoxBtn: NSButton!
2017-01-11 16:44:58 +08:00
@IBOutlet weak var kcptunCheckBoxBtn: NSButton!
@IBOutlet weak var kcptunCryptComboBox: NSComboBox!
@IBOutlet weak var kcptunKeyTextField: NSTextField!
@IBOutlet weak var kcptunModeComboBox: NSComboBox!
@IBOutlet weak var kcptunNocompCheckBoxBtn: NSButton!
@IBOutlet weak var kcptunDatashardTextField: NSTextField!
@IBOutlet weak var kcptunParityshardTextField: NSTextField!
2017-01-12 15:31:23 +08:00
@IBOutlet weak var kcptunMTUTextField: NSTextField!
@IBOutlet weak var kcptunArgumentsTextField: NSTextField!
2016-06-09 00:30:08 +08:00
2016-08-22 23:17:31 +08:00
@IBOutlet weak var removeButton: NSButton!
let tableViewDragType: String = "ss.server.profile.data"
var defaults: UserDefaults!
2016-06-09 00:30:08 +08:00
var profileMgr: ServerProfileManager!
var editingProfile: ServerProfile!
2017-03-23 23:19:42 +08:00
var enabledKcptunSubDisosable: Disposable?
2016-06-09 00:30:08 +08:00
2016-08-22 23:17:31 +08:00
2016-06-09 00:30:08 +08:00
override func windowDidLoad() {
super.windowDidLoad()
// Implement this method to handle any initialization after your window controller's window has been loaded from its nib file.
2017-03-23 23:19:42 +08:00
defaults = UserDefaults.standard
profileMgr = ServerProfileManager.instance
2016-06-09 00:30:08 +08:00
methodTextField.addItems(withObjectValues: [
2017-03-22 22:41:18 +08:00
"aes-128-gcm",
"aes-192-gcm",
"aes-256-gcm",
2016-06-09 00:30:08 +08:00
"aes-128-cfb",
"aes-192-cfb",
"aes-256-cfb",
2017-03-22 22:41:18 +08:00
"aes-128-ctr",
"aes-192-ctr",
"aes-256-ctr",
"camellia-128-cfb",
"camellia-192-cfb",
"camellia-256-cfb",
2017-03-22 22:41:18 +08:00
"bf-cfb",
"chacha20-ietf-poly1305",
"salsa20",
2016-06-09 00:30:08 +08:00
"chacha20",
"chacha20-ietf",
2017-03-22 22:41:18 +08:00
"rc4-md5",
2016-06-09 00:30:08 +08:00
])
2017-01-11 16:44:58 +08:00
kcptunCryptComboBox.addItems(withObjectValues: [
"none",
"aes",
"aes-128",
"aes-192",
"salsa20",
"blowfish",
"twofish",
"cast5",
"3des",
"tea",
"xtea",
"xor",
])
kcptunModeComboBox.addItems(withObjectValues: [
"default",
"normal",
"fast",
"fast2",
2017-01-11 20:49:37 +08:00
"fast3",
2017-01-11 16:44:58 +08:00
])
2016-06-09 00:30:08 +08:00
profilesTableView.reloadData()
updateProfileBoxVisible()
}
override func awakeFromNib() {
profilesTableView.register(forDraggedTypes: [tableViewDragType])
2017-04-15 09:03:42 +08:00
profilesTableView.allowsMultipleSelection = true
}
@IBAction func addProfile(_ sender: NSButton) {
2016-06-09 05:44:18 +08:00
if editingProfile != nil && !editingProfile.isValid(){
2016-08-22 23:17:31 +08:00
shakeWindows()
2016-06-09 00:30:08 +08:00
return
}
profilesTableView.beginUpdates()
let profile = ServerProfile()
profile.remark = "New Server".localized
profileMgr.profiles.append(profile)
let index = IndexSet(integer: profileMgr.profiles.count-1)
profilesTableView.insertRows(at: index, withAnimation: .effectFade)
2016-06-09 00:30:08 +08:00
self.profilesTableView.scrollRowToVisible(self.profileMgr.profiles.count-1)
self.profilesTableView.selectRowIndexes(index, byExtendingSelection: false)
profilesTableView.endUpdates()
updateProfileBoxVisible()
}
@IBAction func removeProfile(_ sender: NSButton) {
2017-04-15 09:03:42 +08:00
let index = Int(profilesTableView.selectedRowIndexes.first!)
var deleteCount = 0
2016-06-09 00:30:08 +08:00
if index >= 0 {
profilesTableView.beginUpdates()
for (_, toDeleteIndex) in profilesTableView.selectedRowIndexes.enumerated() {
2017-04-15 09:03:42 +08:00
print(profileMgr.profiles.count)
profileMgr.profiles.remove(at: toDeleteIndex - deleteCount)
profilesTableView.removeRows(at: IndexSet(integer: toDeleteIndex - deleteCount), withAnimation: .effectFade)
deleteCount += 1
2017-04-15 09:03:42 +08:00
}
2016-06-09 00:30:08 +08:00
profilesTableView.endUpdates()
}
2017-04-15 22:40:25 +08:00
self.profilesTableView.scrollRowToVisible(index-1)
self.profilesTableView.selectRowIndexes(IndexSet(integer: index-1), byExtendingSelection: false)
2016-06-09 00:30:08 +08:00
updateProfileBoxVisible()
}
@IBAction func ok(_ sender: NSButton) {
2016-06-09 00:30:08 +08:00
if editingProfile != nil {
2016-06-09 05:44:18 +08:00
if !editingProfile.isValid() {
2016-06-09 00:30:08 +08:00
// TODO Shake window?
2016-08-22 23:10:33 +08:00
shakeWindows()
2016-06-09 00:30:08 +08:00
return
}
}
profileMgr.save()
window?.performClose(nil)
2016-06-09 00:30:08 +08:00
NotificationCenter.default
2017-03-18 12:20:51 +08:00
.post(name: NOTIFY_SERVER_PROFILES_CHANGED, object: nil)
2016-06-09 00:30:08 +08:00
}
@IBAction func cancel(_ sender: NSButton) {
2016-06-09 00:30:08 +08:00
window?.performClose(self)
}
@IBAction func duplicate(_ sender: Any) {
var copyCount = 0
for (_, toDuplicateIndex) in profilesTableView.selectedRowIndexes.enumerated() {
print(profileMgr.profiles.count)
let profile = profileMgr.profiles[toDuplicateIndex + copyCount]
let duplicateProfile = profile.copy() as! ServerProfile
duplicateProfile.uuid = UUID().uuidString
profileMgr.profiles.insert(duplicateProfile, at:toDuplicateIndex + copyCount)
profilesTableView.beginUpdates()
let index = IndexSet(integer: toDuplicateIndex + copyCount)
profilesTableView.insertRows(at: index, withAnimation: .effectFade)
self.profilesTableView.scrollRowToVisible(toDuplicateIndex + copyCount)
self.profilesTableView.selectRowIndexes(index, byExtendingSelection: false)
profilesTableView.endUpdates()
copyCount += 1
}
updateProfileBoxVisible()
}
@IBAction func copyCurrentProfileURL2Pasteboard(_ sender: NSButton) {
2016-06-10 22:33:27 +08:00
let index = profilesTableView.selectedRow
if index >= 0 {
let profile = profileMgr.profiles[index]
let ssURL = profile.URL()
if let url = ssURL {
// Then copy url to pasteboard
// TODO Why it not working?? It's ok in objective-c
let pboard = NSPasteboard.general()
2016-06-10 22:33:27 +08:00
pboard.clearContents()
let rs = pboard.writeObjects([url as NSPasteboardWriting])
2016-06-10 22:33:27 +08:00
if rs {
NSLog("copy to pasteboard success")
} else {
NSLog("copy to pasteboard failed")
}
}
}
}
2016-06-09 00:30:08 +08:00
func updateProfileBoxVisible() {
2017-01-23 17:50:39 +08:00
if profileMgr.profiles.count <= 0 {
removeButton.isEnabled = false
2016-08-22 23:17:31 +08:00
}else{
removeButton.isEnabled = true
2016-08-22 23:17:31 +08:00
}
2016-06-09 00:30:08 +08:00
if profileMgr.profiles.isEmpty {
profileBox.isHidden = true
2016-06-09 00:30:08 +08:00
} else {
profileBox.isHidden = false
2016-06-09 00:30:08 +08:00
}
}
func bindProfile(_ index:Int) {
2016-06-09 00:30:08 +08:00
NSLog("bind profile \(index)")
2017-03-23 23:19:42 +08:00
if let dis = enabledKcptunSubDisosable {
dis.dispose()
enabledKcptunSubDisosable = Optional.none
}
2016-06-09 00:30:08 +08:00
if index >= 0 && index < profileMgr.profiles.count {
editingProfile = profileMgr.profiles[index]
2017-03-23 23:19:42 +08:00
enabledKcptunSubDisosable = editingProfile.rx.observeWeakly(Bool.self, "enabledKcptun")
.subscribe(onNext: { v in
if let enabled = v {
self.portTextField.isEnabled = !enabled
}
})
hostTextField.bind("value", to: editingProfile, withKeyPath: "serverHost"
2016-06-09 00:30:08 +08:00
, options: [NSContinuouslyUpdatesValueBindingOption: true])
portTextField.bind("value", to: editingProfile, withKeyPath: "serverPort"
2016-06-09 00:30:08 +08:00
, options: [NSContinuouslyUpdatesValueBindingOption: true])
methodTextField.bind("value", to: editingProfile, withKeyPath: "method"
2016-06-09 00:30:08 +08:00
, options: [NSContinuouslyUpdatesValueBindingOption: true])
passwordTextField.bind("value", to: editingProfile, withKeyPath: "password"
2016-06-09 00:30:08 +08:00
, options: [NSContinuouslyUpdatesValueBindingOption: true])
remarkTextField.bind("value", to: editingProfile, withKeyPath: "remark"
2016-06-09 00:30:08 +08:00
, options: [NSContinuouslyUpdatesValueBindingOption: true])
otaCheckBoxBtn.bind("value", to: editingProfile, withKeyPath: "ota"
, options: [NSContinuouslyUpdatesValueBindingOption: true])
2017-01-11 16:44:58 +08:00
// --------------------------------------------------
// Kcptun
kcptunCheckBoxBtn.bind("value", to: editingProfile, withKeyPath: "enabledKcptun"
, options: [NSContinuouslyUpdatesValueBindingOption: true])
2017-03-23 23:19:42 +08:00
kcptunPortTextField.bind("value", to: editingProfile, withKeyPath: "serverPort"
, options: [NSContinuouslyUpdatesValueBindingOption: true])
2017-01-11 16:44:58 +08:00
kcptunProfileBox.bind("Hidden", to: editingProfile, withKeyPath: "enabledKcptun"
, options: [NSContinuouslyUpdatesValueBindingOption: false,
NSValueTransformerNameBindingOption: NSValueTransformerName.negateBooleanTransformerName])
kcptunNocompCheckBoxBtn.bind("value", to: editingProfile, withKeyPath: "kcptunProfile.nocomp", options: nil)
kcptunModeComboBox.bind("value", to: editingProfile, withKeyPath: "kcptunProfile.mode", options: nil)
kcptunCryptComboBox.bind("value", to: editingProfile, withKeyPath: "kcptunProfile.crypt", options: nil)
kcptunKeyTextField.bind("value", to: editingProfile, withKeyPath: "kcptunProfile.key"
, options: [NSContinuouslyUpdatesValueBindingOption: true])
kcptunDatashardTextField.bind("value", to: editingProfile, withKeyPath: "kcptunProfile.datashard"
, options: [NSContinuouslyUpdatesValueBindingOption: true])
kcptunParityshardTextField.bind("value", to: editingProfile, withKeyPath: "kcptunProfile.parityshard"
, options: [NSContinuouslyUpdatesValueBindingOption: true])
2017-01-12 15:31:23 +08:00
kcptunMTUTextField.bind("value", to: editingProfile, withKeyPath: "kcptunProfile.mtu"
, options: [NSContinuouslyUpdatesValueBindingOption: true])
kcptunArgumentsTextField.bind("value", to: editingProfile, withKeyPath: "kcptunProfile.arguments"
, options: [NSContinuouslyUpdatesValueBindingOption: true])
2016-06-09 00:30:08 +08:00
} else {
editingProfile = nil
hostTextField.unbind("value")
portTextField.unbind("value")
methodTextField.unbind("value")
passwordTextField.unbind("value")
remarkTextField.unbind("value")
otaCheckBoxBtn.unbind("value")
2017-01-11 16:44:58 +08:00
kcptunCheckBoxBtn.unbind("value")
2016-06-09 00:30:08 +08:00
}
}
func getDataAtRow(_ index:Int) -> (String, Bool) {
2016-06-09 00:30:08 +08:00
let profile = profileMgr.profiles[index]
let isActive = (profileMgr.activeProfileId == profile.uuid)
if !profile.remark.isEmpty {
return (profile.remark, isActive)
} else {
return (profile.serverHost, isActive)
}
}
//--------------------------------------------------
// For NSTableViewDataSource
func numberOfRows(in tableView: NSTableView) -> Int {
2016-06-09 00:30:08 +08:00
if let mgr = profileMgr {
return mgr.profiles.count
}
return 0
}
func tableView(_ tableView: NSTableView
, objectValueFor tableColumn: NSTableColumn?
, row: Int) -> Any? {
2016-06-09 00:30:08 +08:00
let (title, isActive) = getDataAtRow(row)
if tableColumn?.identifier == "main" {
return title
} else if tableColumn?.identifier == "status" {
if isActive {
return NSImage(named: "NSMenuOnStateTemplate")
} else {
return nil
}
}
return ""
}
// Drag & Drop reorder rows
func tableView(_ tableView: NSTableView, pasteboardWriterForRow row: Int) -> NSPasteboardWriting? {
let item = NSPasteboardItem()
item.setString(String(row), forType: tableViewDragType)
return item
}
func tableView(_ tableView: NSTableView, validateDrop info: NSDraggingInfo, proposedRow row: Int
, proposedDropOperation dropOperation: NSTableViewDropOperation) -> NSDragOperation {
if dropOperation == .above {
return .move
}
return NSDragOperation()
}
func tableView(_ tableView: NSTableView, acceptDrop info: NSDraggingInfo
, row: Int, dropOperation: NSTableViewDropOperation) -> Bool {
if let mgr = profileMgr {
var oldIndexes = [Int]()
info.enumerateDraggingItems(options: [], for: tableView, classes: [NSPasteboardItem.self], searchOptions: [:]) {
if let str = ($0.0.item as! NSPasteboardItem).string(forType: self.tableViewDragType), let index = Int(str) {
oldIndexes.append(index)
}
}
var oldIndexOffset = 0
var newIndexOffset = 0
// For simplicity, the code below uses `tableView.moveRowAtIndex` to move rows around directly.
// You may want to move rows in your content array and then call `tableView.reloadData()` instead.
tableView.beginUpdates()
for oldIndex in oldIndexes {
if oldIndex < row {
let o = mgr.profiles.remove(at: oldIndex + oldIndexOffset)
mgr.profiles.insert(o, at:row - 1)
tableView.moveRow(at: oldIndex + oldIndexOffset, to: row - 1)
oldIndexOffset -= 1
} else {
let o = mgr.profiles.remove(at: oldIndex)
mgr.profiles.insert(o, at:row + newIndexOffset)
tableView.moveRow(at: oldIndex, to: row + newIndexOffset)
newIndexOffset += 1
}
}
tableView.endUpdates()
return true
}
return false
}
2016-06-09 00:30:08 +08:00
//--------------------------------------------------
// For NSTableViewDelegate
func tableView(_ tableView: NSTableView
, shouldEdit tableColumn: NSTableColumn?, row: Int) -> Bool {
2016-06-09 00:30:08 +08:00
return false
}
func tableView(_ tableView: NSTableView, shouldSelectRow row: Int) -> Bool {
2016-06-09 00:30:08 +08:00
if row < 0 {
editingProfile = nil
return true
}
if editingProfile != nil {
2016-06-09 05:44:18 +08:00
if !editingProfile.isValid() {
2016-06-09 00:30:08 +08:00
return false
}
}
return true
}
func tableViewSelectionDidChange(_ notification: Notification) {
2016-06-09 00:30:08 +08:00
if profilesTableView.selectedRow >= 0 {
bindProfile(profilesTableView.selectedRow)
} else {
if !profileMgr.profiles.isEmpty {
let index = IndexSet(integer: profileMgr.profiles.count - 1)
2016-06-09 00:30:08 +08:00
profilesTableView.selectRowIndexes(index, byExtendingSelection: false)
}
}
}
2016-08-22 23:10:33 +08:00
func shakeWindows(){
let numberOfShakes:Int = 8
let durationOfShake:Float = 0.5
let vigourOfShake:Float = 0.05
let frame:CGRect = (window?.frame)!
let shakeAnimation = CAKeyframeAnimation()
let shakePath = CGMutablePath()
shakePath.move(to: CGPoint(x:NSMinX(frame), y:NSMinY(frame)))
2016-08-22 23:10:33 +08:00
for _ in 1...numberOfShakes{
shakePath.addLine(to: CGPoint(x: NSMinX(frame) - frame.size.width * CGFloat(vigourOfShake), y: NSMinY(frame)))
shakePath.addLine(to: CGPoint(x: NSMinX(frame) + frame.size.width * CGFloat(vigourOfShake), y: NSMinY(frame)))
2016-08-22 23:10:33 +08:00
}
shakePath.closeSubpath()
2016-08-22 23:10:33 +08:00
shakeAnimation.path = shakePath
shakeAnimation.duration = CFTimeInterval(durationOfShake)
window?.animations = ["frameOrigin":shakeAnimation]
window?.animator().setFrameOrigin(window!.frame.origin)
}
2016-06-09 00:30:08 +08:00
}