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]()
|
2016-11-13 11:03:51 +00:00
|
|
|
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!
|
2015-01-02 15:17:10 +00:00
|
|
|
var termination : AnyObject!
|
2016-11-13 11:03:51 +00:00
|
|
|
dynamic var portHandle : FileHandle?
|
|
|
|
var currentTheme : ACETheme = .xcode
|
2014-12-31 03:16:43 +00:00
|
|
|
var fontSize : UInt = 12
|
|
|
|
var portDefaults = [String: AnyObject]()
|
2014-12-31 07:26:45 +00:00
|
|
|
var shouldReconnect = false
|
2016-11-13 11:03:51 +00:00
|
|
|
dynamic var task : Task?
|
2015-01-02 15:17:10 +00:00
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
2016-11-13 11:03:51 +00:00
|
|
|
class func showWindowWithPort(port: String, task: Task, speed: Int) {
|
2015-01-02 15:17:10 +00:00
|
|
|
if let existing = serialInstances[port] {
|
2016-11-13 11:03:51 +00:00
|
|
|
existing.showWindowWithTask(task: task, speed:speed)
|
2015-01-02 15:17:10 +00:00
|
|
|
} else {
|
|
|
|
let newInstance = ASSerialWin(port:port)
|
|
|
|
serialInstances[port] = newInstance
|
2016-11-13 11:03:51 +00:00
|
|
|
newInstance.showWindowWithTask(task: task, speed:speed)
|
2015-01-02 15:17:10 +00:00
|
|
|
}
|
|
|
|
}
|
2014-12-31 07:26:45 +00:00
|
|
|
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
|
|
|
|
2014-12-31 03:20:39 +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
|
|
|
|
2016-11-13 11:03:51 +00:00
|
|
|
let userDefaults = UserDefaults.standard
|
2014-12-31 03:16:43 +00:00
|
|
|
|
2016-11-13 11:03:51 +00:00
|
|
|
if let portDef = (userDefaults.object(forKey:"SerialDefaults") as! NSDictionary).object(forKey:port) as? [String: AnyObject] {
|
2014-12-31 03:16:43 +00:00
|
|
|
portDefaults = portDef
|
|
|
|
} else {
|
2016-11-13 11:03:51 +00:00
|
|
|
portDefaults["Theme"] = userDefaults.string(forKey:"SerialTheme")
|
|
|
|
portDefaults["FontSize"] = userDefaults.object(forKey:"FontSize")
|
2014-12-31 03:16:43 +00:00
|
|
|
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
|
|
|
}
|
2016-11-13 11:03:51 +00:00
|
|
|
if let themeId = ACEView.themeIdByName(themeName: 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
|
|
|
|
2016-11-13 11:03:51 +00:00
|
|
|
if let handlerName = userDefaults.string(forKey:"Bindings") {
|
|
|
|
if let handlerId = ACEView.handlerIdByName(handlerName: handlerName) {
|
2014-12-31 03:16:43 +00:00
|
|
|
keyboardHandler = handlerId
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-13 11:03:51 +00:00
|
|
|
let nc = NotificationCenter.default
|
2016-11-13 12:13:40 +00:00
|
|
|
serialObserver = nc.addObserver(forName: NSNotification.Name(kASSerialPortsChanged), object: nil, queue: nil, using: { (NSNotification) in
|
|
|
|
self.willChangeValue(forKey: "hasValidPort")
|
|
|
|
self.didChangeValue(forKey: "hasValidPort")
|
2014-12-31 07:26:45 +00:00
|
|
|
|
2015-01-02 15:17:10 +00:00
|
|
|
if self.task == nil {
|
|
|
|
if self.hasValidPort {
|
|
|
|
self.reconnect()
|
|
|
|
} else {
|
|
|
|
self.disconnectTemporarily()
|
|
|
|
}
|
2014-12-31 07:26:45 +00:00
|
|
|
}
|
2014-12-29 06:35:07 +00:00
|
|
|
})
|
2016-11-13 12:13:40 +00:00
|
|
|
termination = NotificationCenter.default.addObserver(forName: Task.didTerminateNotification,
|
|
|
|
object: nil, queue: nil, using:
|
|
|
|
{ (notification: Notification) in
|
|
|
|
if notification.object as? Task == self.task {
|
2015-01-02 15:17:10 +00:00
|
|
|
self.task = nil
|
|
|
|
self.portHandle = nil
|
|
|
|
}
|
|
|
|
})
|
2014-12-29 06:35:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
override func finalize() {
|
2015-01-02 15:17:10 +00:00
|
|
|
if portHandle != nil {
|
|
|
|
connect(self)
|
|
|
|
}
|
2016-11-13 11:03:51 +00:00
|
|
|
NotificationCenter.default.removeObserver(serialObserver)
|
|
|
|
NotificationCenter.default.removeObserver(termination)
|
|
|
|
serialInstances.removeValue(forKey: port)
|
2014-12-29 06:35:07 +00:00
|
|
|
}
|
2015-01-02 15:17:10 +00:00
|
|
|
|
|
|
|
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)
|
2016-11-13 11:03:51 +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
|
2015-01-02 15:17:10 +00:00
|
|
|
if task == nil {
|
|
|
|
connect(self)
|
|
|
|
}
|
2014-12-29 06:35:07 +00:00
|
|
|
super.windowDidLoad()
|
|
|
|
}
|
2015-01-02 15:17:10 +00:00
|
|
|
|
2016-11-13 11:03:51 +00:00
|
|
|
func installReader(handle: FileHandle?) {
|
2015-01-02 15:17:10 +00:00
|
|
|
if let readHandle = handle {
|
|
|
|
serialData = ""
|
|
|
|
logView.setString(serialData)
|
|
|
|
readHandle.readabilityHandler = {(handle) in
|
|
|
|
let newData = handle.availableDataIgnoringExceptions()
|
2016-11-13 11:03:51 +00:00
|
|
|
let newString = NSString(data: newData, encoding: String.Encoding.ascii) as! String
|
2015-01-02 15:17:10 +00:00
|
|
|
self.serialData += newString
|
2016-11-13 11:03:51 +00:00
|
|
|
DispatchQueue.main.async(execute: { () -> Void in
|
2015-01-02 15:17:10 +00:00
|
|
|
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" : "")
|
2016-11-13 11:03:51 +00:00
|
|
|
let data = line.data(using: String.Encoding.ascii, allowLossyConversion: true)!
|
|
|
|
portHandle?.write(data)
|
2014-12-29 06:35:07 +00:00
|
|
|
}
|
|
|
|
|
2016-11-13 11:03:51 +00:00
|
|
|
func showWindowWithTask(task: Task, speed:Int) {
|
2015-01-02 15:17:10 +00:00
|
|
|
if portHandle != nil {
|
|
|
|
connect(self)
|
|
|
|
}
|
|
|
|
baudRate = speed
|
|
|
|
self.task = task
|
2016-11-13 11:03:51 +00:00
|
|
|
portHandle = (task.standardInput as! Pipe).fileHandleForWriting
|
2015-01-02 15:17:10 +00:00
|
|
|
showWindow(self)
|
2016-11-13 11:03:51 +00:00
|
|
|
installReader(handle: (task.standardOutput as? Pipe)?.fileHandleForReading)
|
2015-01-02 15:17:10 +00:00
|
|
|
}
|
|
|
|
|
2015-11-16 01:56:33 +00:00
|
|
|
@IBAction func connect(_: AnyObject) {
|
2014-12-31 07:26:45 +00:00
|
|
|
shouldReconnect = false
|
2015-01-02 15:17:10 +00:00
|
|
|
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))
|
2016-11-13 11:03:51 +00:00
|
|
|
installReader(handle: portHandle)
|
2014-12-29 06:35:07 +00:00
|
|
|
}
|
|
|
|
}
|
2014-12-31 07:26:45 +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")
|
|
|
|
}
|
2014-12-31 07:26:45 +00:00
|
|
|
var hasValidPort : Bool {
|
|
|
|
get {
|
2015-11-16 01:56:33 +00:00
|
|
|
return ASSerial.ports().contains(port)
|
2014-12-31 07:26:45 +00:00
|
|
|
}
|
|
|
|
}
|
2014-12-31 03:16:43 +00:00
|
|
|
|
|
|
|
// MARK: Editor configuration
|
|
|
|
|
|
|
|
@IBAction func changeTheme(item: NSMenuItem) {
|
2016-11-13 11:03:51 +00:00
|
|
|
let userDefaults = UserDefaults.standard
|
|
|
|
currentTheme = ACETheme(rawValue: UInt(item.tag)) ?? .xcode
|
2014-12-31 03:16:43 +00:00
|
|
|
logView.setTheme(currentTheme)
|
2016-11-13 11:03:51 +00:00
|
|
|
let themeName = ACEThemeNames.humanName(for: currentTheme)
|
|
|
|
userDefaults.set(themeName, forKey: "SerialTheme")
|
2014-12-31 03:16:43 +00:00
|
|
|
portDefaults["Theme"] = themeName
|
|
|
|
updatePortDefaults()
|
|
|
|
}
|
|
|
|
@IBAction func changeKeyboardHandler(item: NSMenuItem) {
|
|
|
|
keyboardHandler = ACEKeyboardHandler(rawValue: UInt(item.tag))!
|
2016-11-13 11:03:51 +00:00
|
|
|
UserDefaults.standard.set(
|
|
|
|
ACEKeyboardHandlerNames.humanName(for: keyboardHandler), forKey: "Bindings")
|
2016-11-13 12:13:40 +00:00
|
|
|
NotificationCenter.default.post(name: Notification.Name("Bindings"), object: item)
|
2014-12-31 03:16:43 +00:00
|
|
|
}
|
2016-11-13 12:13:40 +00:00
|
|
|
|
2014-12-31 03:16:43 +00:00
|
|
|
func validateUserInterfaceItem(anItem: NSValidatedUserInterfaceItem) -> Bool {
|
|
|
|
if let menuItem = anItem as? NSMenuItem {
|
2016-11-13 11:03:51 +00:00
|
|
|
if menuItem.action == Selector(("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
|
2016-11-13 11:03:51 +00:00
|
|
|
} else if menuItem.action == Selector(("changeKeyboardHandler:")) {
|
2014-12-31 03:16:43 +00:00
|
|
|
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() {
|
2016-11-13 11:03:51 +00:00
|
|
|
let userDefaults = UserDefaults.standard
|
|
|
|
let sd = userDefaults.object(forKey:"SerialDefaults") as! [String: AnyObject]
|
2015-02-14 16:43:20 +00:00
|
|
|
let serialDefaults = NSMutableDictionary(dictionary: sd)
|
2015-01-02 15:17:10 +00:00
|
|
|
serialDefaults.setValue(NSDictionary(dictionary:portDefaults), forKey:port)
|
2016-11-13 11:03:51 +00:00
|
|
|
userDefaults.set(serialDefaults, forKey:"SerialDefaults")
|
2014-12-31 03:16:43 +00:00
|
|
|
}
|
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
|
2016-11-13 11:03:51 +00:00
|
|
|
savePanel.isExtensionHidden = false
|
|
|
|
savePanel.beginSheetModal(for: window!, completionHandler: { (returnCode) -> Void in
|
2014-12-31 11:56:20 +00:00
|
|
|
if returnCode == NSFileHandlingPanelOKButton {
|
2015-11-16 01:56:33 +00:00
|
|
|
do {
|
2016-11-13 11:03:51 +00:00
|
|
|
try self.serialData.write(to: savePanel.url!, atomically:false, encoding:String.Encoding.utf8)
|
2015-11-16 01:56:33 +00:00
|
|
|
} catch _ {
|
|
|
|
}
|
2014-12-31 11:56:20 +00:00
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
2014-12-29 06:35:07 +00:00
|
|
|
}
|