AVRSack/AVRsack/ASSerialWin.swift

305 lines
11 KiB
Swift
Raw Permalink 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]()
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!
2016-11-21 19:33:02 +00:00
var portDefaults = [String: Any]()
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!
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 shouldReconnect = false
2016-11-21 19:33:02 +00:00
dynamic var task : Process?
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-21 19:33:02 +00:00
class func showWindowWithPort(port: String, task: Process, speed: Int) {
if let existing = serialInstances[port] {
2016-11-13 11:03:51 +00:00
existing.showWindowWithTask(task: task, speed:speed)
} else {
let newInstance = ASSerialWin(port:port)
serialInstances[port] = newInstance
2016-11-13 11:03:51 +00:00
newInstance.showWindowWithTask(task: 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
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")
if self.task == nil {
if self.hasValidPort {
self.reconnect()
} else {
self.disconnectTemporarily()
}
}
2014-12-29 06:35:07 +00:00
})
2016-11-21 19:33:02 +00:00
termination = NotificationCenter.default.addObserver(forName: Process.didTerminateNotification,
2016-11-13 12:13:40 +00:00
object: nil, queue: nil, using:
{ (notification: Notification) in
2016-11-21 19:33:02 +00:00
if notification.object as? Process == self.task {
self.task = nil
self.portHandle = nil
}
})
2014-12-29 06:35:07 +00:00
}
override func finalize() {
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
}
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
if task == nil {
connect(self)
}
2014-12-29 06:35:07 +00:00
super.windowDidLoad()
}
2016-11-13 11:03:51 +00:00
func installReader(handle: FileHandle?) {
if let readHandle = handle {
serialData = ""
logView.setString(serialData)
readHandle.readabilityHandler = {(handle) in
2016-11-13 12:27:19 +00:00
if let newData = handle.availableDataIgnoringExceptions(),
let newString = String(data: newData, encoding: String.Encoding.ascii)
{
self.serialData += newString
DispatchQueue.main.async(execute: {
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-21 19:33:02 +00:00
func showWindowWithTask(task: Process, speed:Int) {
if portHandle != nil {
connect(self)
}
baudRate = speed
self.task = task
2016-11-13 11:03:51 +00:00
portHandle = (task.standardInput as! Pipe).fileHandleForWriting
showWindow(self)
2016-11-13 11:03:51 +00:00
installReader(handle: (task.standardOutput as? Pipe)?.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))
2016-11-13 11:03:51 +00:00
installReader(handle: 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
2016-11-14 00:58:22 +00:00
@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()
}
2016-11-14 00:58:22 +00:00
@IBAction func changeKeyboardHandler(_ item: NSMenuItem) {
2014-12-31 03:16:43 +00:00
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-14 00:58:22 +00:00
if menuItem.action == #selector(ASSerialWin.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-14 00:58:22 +00:00
} else if menuItem.action == #selector(ASSerialWin.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)
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
}