AVRSack/AVRsack/ASSerialWin.swift

303 lines
11 KiB
Swift
Raw Normal View History

2014-12-29 06:35:07 +00:00
//
// ASSerialWin.swift
// AVRsack
//
// Created by Matthias Neeracher on 27/12/14.
// Copyright (c) 2014 Aere Perennius. All rights reserved.
//
import Cocoa
2014-12-31 03:16:43 +00:00
private var serialInstances = [String : ASSerialWin]()
private var keyboardHandler : ACEKeyboardHandler = .Ace
2014-12-29 06:35:07 +00:00
class ASSerialWin: NSWindowController {
@IBOutlet weak var inputLine : NSTextField!
@IBOutlet weak var logView : ACEView!
2014-12-31 07:40:08 +00:00
var baudRate : Int = 9600 {
2014-12-29 06:35:07 +00:00
didSet(oldRate) {
if portHandle != nil {
connect(self) // Disconnect existing
connect(self) // Reconnect
}
2014-12-31 07:40:08 +00:00
portDefaults["BaudRate"] = baudRate
updatePortDefaults()
2014-12-29 06:35:07 +00:00
}
}
var sendCR = false
var sendLF = true
2014-12-31 03:16:43 +00:00
var scrollToBottom : Bool = true {
didSet(oldScroll) {
if scrollToBottom {
logView.gotoLine(1000000000, column: 0, animated: true)
}
}
}
2014-12-29 06:35:07 +00:00
var port = ""
var serialData = ""
var serialObserver : AnyObject!
var termination : AnyObject!
2014-12-29 06:35:07 +00:00
dynamic var portHandle : NSFileHandle?
2015-03-14 20:15:27 +00:00
var currentTheme : ACETheme = .Xcode
2014-12-31 03:16:43 +00:00
var fontSize : UInt = 12
var portDefaults = [String: AnyObject]()
var shouldReconnect = false
dynamic var task : NSTask?
2014-12-29 06:35:07 +00:00
class func showWindowWithPort(port: String) {
if let existing = serialInstances[port] {
existing.showWindow(self)
} else {
let newInstance = ASSerialWin(port:port)
serialInstances[port] = newInstance
newInstance.showWindow(self)
}
}
class func showWindowWithPort(port: String, task: NSTask, speed: Int) {
if let existing = serialInstances[port] {
existing.showWindowWithTask(task, speed:speed)
} else {
let newInstance = ASSerialWin(port:port)
serialInstances[port] = newInstance
newInstance.showWindowWithTask(task, speed:speed)
}
}
class func portNeededForUpload(port: String) {
if let existing = serialInstances[port] {
existing.disconnectTemporarily()
}
}
class func portAvailableAfterUpload(port: String) {
if let existing = serialInstances[port] {
existing.reconnect()
}
}
2014-12-29 06:35:07 +00:00
convenience init(port: String) {
self.init(windowNibName:"ASSerialWin")
2014-12-29 06:35:07 +00:00
self.port = port
2014-12-31 03:16:43 +00:00
let userDefaults = NSUserDefaults.standardUserDefaults()
2015-02-14 16:43:20 +00:00
if let portDef = (userDefaults.objectForKey("SerialDefaults") as! NSDictionary).objectForKey(port) as? [String: AnyObject] {
2014-12-31 03:16:43 +00:00
portDefaults = portDef
} else {
portDefaults["Theme"] = userDefaults.stringForKey("SerialTheme")
portDefaults["FontSize"] = userDefaults.objectForKey("FontSize")
portDefaults["SendCR"] = sendCR
portDefaults["SendLF"] = sendLF
2014-12-31 07:40:08 +00:00
portDefaults["BaudRate"] = 19200
2014-12-31 03:16:43 +00:00
}
2015-02-14 16:43:20 +00:00
if let themeId = ACEView.themeIdByName(portDefaults["Theme"] as! String) {
2014-12-31 03:16:43 +00:00
currentTheme = themeId
}
2015-02-14 16:43:20 +00:00
fontSize = portDefaults["FontSize"] as! UInt
sendCR = portDefaults["SendCR"] as! Bool
sendLF = portDefaults["SendLF"] as! Bool
baudRate = portDefaults["BaudRate"] as! Int
2014-12-31 03:16:43 +00:00
if let handlerName = userDefaults.stringForKey("Bindings") {
if let handlerId = ACEView.handlerIdByName(handlerName) {
keyboardHandler = handlerId
}
}
2015-11-16 01:56:33 +00:00
let nc = NSNotificationCenter.defaultCenter()
2014-12-29 06:35:07 +00:00
serialObserver = nc.addObserverForName(kASSerialPortsChanged, object: nil, queue: nil, usingBlock: { (NSNotification) in
self.willChangeValueForKey("hasValidPort")
self.didChangeValueForKey("hasValidPort")
if self.task == nil {
if self.hasValidPort {
self.reconnect()
} else {
self.disconnectTemporarily()
}
}
2014-12-29 06:35:07 +00:00
})
termination = NSNotificationCenter.defaultCenter().addObserverForName(NSTaskDidTerminateNotification,
object: nil, queue: nil, usingBlock:
2015-11-16 01:56:33 +00:00
{ (notification: NSNotification) in
if notification.object as? NSTask == self.task {
self.task = nil
self.portHandle = nil
}
})
2014-12-29 06:35:07 +00:00
}
override func finalize() {
if portHandle != nil {
connect(self)
}
2014-12-29 06:35:07 +00:00
NSNotificationCenter.defaultCenter().removeObserver(serialObserver)
NSNotificationCenter.defaultCenter().removeObserver(termination)
2014-12-29 06:35:07 +00:00
serialInstances.removeValueForKey(port)
}
func windowWillClose(notification: NSNotification) {
if portHandle != nil {
connect(self)
}
}
2014-12-29 06:35:07 +00:00
override func windowDidLoad() {
logView.setReadOnly(true)
logView.setShowPrintMargin(false)
2014-12-31 03:16:43 +00:00
logView.setTheme(currentTheme)
logView.setKeyboardHandler(keyboardHandler)
logView.setFontSize(fontSize)
2015-03-14 20:15:27 +00:00
logView.setMode(.Text)
2014-12-31 03:16:43 +00:00
logView.alphaValue = 0.8
2014-12-29 06:35:07 +00:00
window?.title = port
if task == nil {
connect(self)
}
2014-12-29 06:35:07 +00:00
super.windowDidLoad()
}
func installReader(handle: NSFileHandle?) {
if let readHandle = handle {
serialData = ""
logView.setString(serialData)
readHandle.readabilityHandler = {(handle) in
let newData = handle.availableDataIgnoringExceptions()
2015-02-14 16:43:20 +00:00
let newString = NSString(data: newData, encoding: NSASCIIStringEncoding) as! String
self.serialData += newString
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.logView.setString(self.serialData)
if self.scrollToBottom {
self.logView.gotoLine(1000000000, column: 0, animated: true)
}
})
}
}
2014-12-29 06:35:07 +00:00
}
2015-11-16 01:56:33 +00:00
@IBAction func sendInput(_: AnyObject) {
2014-12-29 07:33:35 +00:00
let line = inputLine.stringValue + (sendCR ? "\r" : "") + (sendLF ? "\n" : "")
let data = line.dataUsingEncoding(NSASCIIStringEncoding, allowLossyConversion: true)!
portHandle?.writeData(data)
2014-12-29 06:35:07 +00:00
}
func showWindowWithTask(task: NSTask, speed:Int) {
if portHandle != nil {
connect(self)
}
baudRate = speed
self.task = task
2015-02-14 16:43:20 +00:00
portHandle = (task.standardInput as! NSPipe).fileHandleForWriting
showWindow(self)
installReader((task.standardOutput as? NSPipe)?.fileHandleForReading)
}
2015-11-16 01:56:33 +00:00
@IBAction func connect(_: AnyObject) {
shouldReconnect = false
if task != nil {
task!.interrupt()
} else if portHandle != nil {
let fd = portHandle!.fileDescriptor
ASSerial.restorePort(fd)
ASSerial.closePort(fd)
2014-12-29 06:35:07 +00:00
portHandle = nil
} else {
2014-12-31 07:40:08 +00:00
portHandle = ASSerial.openPort(port, withSpeed: Int32(baudRate))
installReader(portHandle)
2014-12-29 06:35:07 +00:00
}
}
func disconnectTemporarily() {
if portHandle != nil {
connect(self) // Disconnect temporarily
shouldReconnect = true // But express interest to reconnect
}
}
func reconnect() {
if portHandle == nil && shouldReconnect {
connect(self) // Reconnect
}
}
2014-12-29 06:35:07 +00:00
var connectButtonTitle : String {
get {
return (portHandle != nil) ? "Disconnect" : "Connect"
}
}
class func keyPathsForValuesAffectingConnectButtonTitle() -> NSSet {
return NSSet(object: "portHandle")
}
var hasValidPort : Bool {
get {
2015-11-16 01:56:33 +00:00
return ASSerial.ports().contains(port)
}
}
2014-12-31 03:16:43 +00:00
// MARK: Editor configuration
@IBAction func changeTheme(item: NSMenuItem) {
let userDefaults = NSUserDefaults.standardUserDefaults()
2015-03-14 20:15:27 +00:00
currentTheme = ACETheme(rawValue: UInt(item.tag)) ?? .Xcode
2014-12-31 03:16:43 +00:00
logView.setTheme(currentTheme)
let themeName = ACEThemeNames.humanNameForTheme(currentTheme)
userDefaults.setObject(themeName, forKey: "SerialTheme")
portDefaults["Theme"] = themeName
updatePortDefaults()
}
@IBAction func changeKeyboardHandler(item: NSMenuItem) {
keyboardHandler = ACEKeyboardHandler(rawValue: UInt(item.tag))!
NSUserDefaults.standardUserDefaults().setObject(
ACEKeyboardHandlerNames.humanNameForKeyboardHandler(keyboardHandler), forKey: "Bindings")
NSNotificationCenter.defaultCenter().postNotificationName("Bindings", object: item)
}
func validateUserInterfaceItem(anItem: NSValidatedUserInterfaceItem) -> Bool {
if let menuItem = anItem as? NSMenuItem {
if menuItem.action == "changeTheme:" {
2015-03-14 20:15:27 +00:00
menuItem.state = (UInt(menuItem.tag) == currentTheme.rawValue ? NSOnState : NSOffState)
2014-12-31 03:16:43 +00:00
return true
} else if menuItem.action == "changeKeyboardHandler:" {
menuItem.state = (menuItem.tag == Int(keyboardHandler.rawValue) ? NSOnState : NSOffState)
return true
}
}
return true
}
2015-11-16 01:56:33 +00:00
@IBAction func makeTextLarger(_: AnyObject) {
2014-12-31 03:16:43 +00:00
fontSize += 1
logView.setFontSize(fontSize)
portDefaults["FontSize"] = fontSize
updatePortDefaults()
}
2015-11-16 01:56:33 +00:00
@IBAction func makeTextSmaller(_: AnyObject) {
2014-12-31 03:16:43 +00:00
if fontSize > 6 {
fontSize -= 1
logView.setFontSize(fontSize)
portDefaults["FontSize"] = fontSize
updatePortDefaults()
}
}
func updatePortDefaults() {
2015-02-14 16:43:20 +00:00
let userDefaults = NSUserDefaults.standardUserDefaults()
let sd = userDefaults.objectForKey("SerialDefaults") as! [String: AnyObject]
let serialDefaults = NSMutableDictionary(dictionary: sd)
serialDefaults.setValue(NSDictionary(dictionary:portDefaults), forKey:port)
2014-12-31 03:16:43 +00:00
userDefaults.setObject(serialDefaults, forKey:"SerialDefaults")
}
2014-12-31 11:56:20 +00:00
2015-11-16 01:56:33 +00:00
@IBAction func saveDocument(_: AnyObject) {
2014-12-31 11:56:20 +00:00
let savePanel = NSSavePanel()
savePanel.allowedFileTypes = ["log"]
savePanel.allowsOtherFileTypes = true
savePanel.extensionHidden = false
savePanel.beginSheetModalForWindow(window!, completionHandler: { (returnCode) -> Void in
if returnCode == NSFileHandlingPanelOKButton {
2015-11-16 01:56:33 +00:00
do {
try self.serialData.writeToURL(savePanel.URL!, atomically:false, encoding:NSUTF8StringEncoding)
} catch _ {
}
2014-12-31 11:56:20 +00:00
}
})
}
2014-12-29 06:35:07 +00:00
}