// // ViewController.swift // Steve ][ // // Created by Tamas Rudnai on 7/25/19. // Copyright Β© 2019, 2020 Tamas Rudnai. All rights reserved. // // This file is part of Steve ][ -- The Apple ][ Emulator. // // Steve ][ is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Steve ][ is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Steve ][. If not, see . // import Cocoa //import AVFoundation //import Metal // //var device : MTLDevice! //var metalLayer: CAMetalLayer! //var vertexBuffer: MTLBuffer! //var pipelineState: MTLRenderPipelineState! //var commandQueue: MTLCommandQueue! ////var timer: CADisplayLink! let K : Double = 1000.0 let M : Double = (K * K) let G : Double = (M * K) let T : Double = (G * K) let KB : Double = 1024.0 let MB : Double = (KB * KB) let GB : Double = (MB * KB) let TB : Double = (GB * KB) let colorWhite = NSColor.init( red:0.9296875, green:0.9296875, blue:0.9296875, alpha: 1 ) let colorGreen = NSColor.init( red:0.16796875, green:0.84375, blue:0.2890625, alpha: 1 ) let colorOrange = NSColor.init( red:1, green:0.38671875, blue:0.0078125, alpha: 1 ) var monoColor = colorGreen; //var spk_up: AVAudioPlayer? //var spk_dn: AVAudioPlayer? // //@_cdecl("ViewController_spk_up_play") //func spk_up_play() { // spk_up?.stop() // spk_dn?.stop() // spk_up?.play() //} // //@_cdecl("ViewController_spk_dn_play") //func spk_dn_play() { // spk_up?.stop() // spk_dn?.stop() // spk_dn?.play() //} #if METAL_YES import Metal #endif class ViewController: NSViewController { static var shared : ViewController? = nil var displayLink: CVDisplayLink? @IBOutlet var monitorView: MonitorView! @IBOutlet weak var textDisplayScroller: NSScrollView! @IBOutlet var textDisplay: NSTextView! @IBOutlet weak var speedometer: NSTextFieldCell! @IBOutlet weak var lores: LoRes! @IBOutlet weak var hires: HiRes! @IBOutlet weak var splashScreen: NSView! @IBOutlet weak var scanLines: NSImageView! var CRTMonitor = false var ColorMonitor = true var Keyboard2Joystick = true var Mouse2Joystick = false var MouseInterface = true // static let charConvStr : String = // "@πŸ„°πŸ„±πŸ„²πŸ„³πŸ„΄πŸ„΅πŸ„ΆπŸ„·πŸ„ΈπŸ„ΉπŸ„ΊπŸ„»πŸ„ΌπŸ„½πŸ„ΎπŸ„ΏπŸ…€πŸ…πŸ…‚πŸ…ƒπŸ…„πŸ……πŸ…†πŸ…‡πŸ…ˆπŸ…‰[\\]^_ !\"#$%&'()*+,-./0123456789:;<=>?" + // "@πŸ…°πŸ…±πŸ…²πŸ…³πŸ…΄πŸ…΅πŸ…ΆπŸ…·πŸ…ΈπŸ…ΉπŸ…ΊπŸ…»πŸ…ΌπŸ…½πŸ…ΎπŸ…ΏπŸ†€πŸ†πŸ†‚πŸ†ƒπŸ†„πŸ†…πŸ††πŸ†‡πŸ†ˆπŸ†‰[\\]^_β¬›οΈŽ!\"#$%&'()*+,-./0123456789:;<=>?" + // FL // "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_ !\"#$%&'()*+,-./0123456789:;<=>?" + // "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~?" // static let charConvStr : String = // "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_ !\"#$%&'()*+,-./0123456789:;<=>?" + // "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_β–‘!\"#$%&'()*+,-./0123456789:;<=>?" + // FL // "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_\u{E0A0}!\"#$%&'()*+,-./0123456789:;<=>?" + // "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~?" // static let charConvStr : String = // "\u{E140}\u{E141}\u{E142}\u{E143}\u{E144}\u{E145}\u{E146}\u{E147}\u{E148}\u{E149}\u{E14A}\u{E14B}\u{E14C}\u{E14D}\u{E14E}\u{E14F}\u{E150}\u{E151}\u{E152}\u{E153}\u{E154}\u{E155}\u{E156}\u{E157}\u{E158}\u{E159}\u{E15A}\u{E15B}\u{E15C}\u{E15D}\u{E15E}\u{E15F}\u{E120}\u{E121}\u{E122}\u{E123}\u{E124}\u{E125}\u{E126}\u{E127}\u{E128}\u{E129}\u{E12A}\u{E12B}\u{E12C}\u{E12D}\u{E12E}\u{E12F}\u{E130}\u{E131}\u{E132}\u{E133}\u{E134}\u{E135}\u{E136}\u{E137}\u{E138}\u{E139}\u{E13A}\u{E13B}\u{E13C}\u{E13D}\u{E13E}\u{E13F}" + // "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_β–‘!\"#$%&'()*+,-./0123456789:;<=>?" + // FL // "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_\u{E0A0}!\"#$%&'()*+,-./0123456789:;<=>?" + // "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~?" // TODO: On 80n col mode no flash + small caps are inversed on the Flash map static let charConvStrFlashOff40 : String = // INVERSE "\u{E140}\u{E141}\u{E142}\u{E143}\u{E144}\u{E145}\u{E146}\u{E147}\u{E148}\u{E149}\u{E14A}\u{E14B}\u{E14C}\u{E14D}\u{E14E}\u{E14F}\u{E150}\u{E151}\u{E152}\u{E153}\u{E154}\u{E155}\u{E156}\u{E157}\u{E158}\u{E159}\u{E15A}\u{E15B}\u{E15C}\u{E15D}\u{E15E}\u{E15F}\u{E120}\u{E121}\u{E122}\u{E123}\u{E124}\u{E125}\u{E126}\u{E127}\u{E128}\u{E129}\u{E12A}\u{E12B}\u{E12C}\u{E12D}\u{E12E}\u{E12F}\u{E130}\u{E131}\u{E132}\u{E133}\u{E134}\u{E135}\u{E136}\u{E137}\u{E138}\u{E139}\u{E13A}\u{E13B}\u{E13C}\u{E13D}\u{E13E}\u{E13F}" + // FLASH "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_ !\"#$%&'()*+,-./0123456789:;<=>?" + // FL "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_ !\"#$%&'()*+,-./0123456789:;<=>?" + "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u{E27F}" static let charConvStrFlashOn40 : String = // INVERSE "\u{E140}\u{E141}\u{E142}\u{E143}\u{E144}\u{E145}\u{E146}\u{E147}\u{E148}\u{E149}\u{E14A}\u{E14B}\u{E14C}\u{E14D}\u{E14E}\u{E14F}\u{E150}\u{E151}\u{E152}\u{E153}\u{E154}\u{E155}\u{E156}\u{E157}\u{E158}\u{E159}\u{E15A}\u{E15B}\u{E15C}\u{E15D}\u{E15E}\u{E15F}\u{E120}\u{E121}\u{E122}\u{E123}\u{E124}\u{E125}\u{E126}\u{E127}\u{E128}\u{E129}\u{E12A}\u{E12B}\u{E12C}\u{E12D}\u{E12E}\u{E12F}\u{E130}\u{E131}\u{E132}\u{E133}\u{E134}\u{E135}\u{E136}\u{E137}\u{E138}\u{E139}\u{E13A}\u{E13B}\u{E13C}\u{E13D}\u{E13E}\u{E13F}" + // FLASH "\u{E140}\u{E141}\u{E142}\u{E143}\u{E144}\u{E145}\u{E146}\u{E147}\u{E148}\u{E149}\u{E14A}\u{E14B}\u{E14C}\u{E14D}\u{E14E}\u{E14F}\u{E150}\u{E151}\u{E152}\u{E153}\u{E154}\u{E155}\u{E156}\u{E157}\u{E158}\u{E159}\u{E15A}\u{E15B}\u{E15C}\u{E15D}\u{E15E}\u{E15F}\u{E120}\u{E121}\u{E122}\u{E123}\u{E124}\u{E125}\u{E126}\u{E127}\u{E128}\u{E129}\u{E12A}\u{E12B}\u{E12C}\u{E12D}\u{E12E}\u{E12F}\u{E130}\u{E131}\u{E132}\u{E133}\u{E134}\u{E135}\u{E136}\u{E137}\u{E138}\u{E139}\u{E13A}\u{E13B}\u{E13C}\u{E13D}\u{E13E}\u{E13F}" + "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_ !\"#$%&'()*+,-./0123456789:;<=>?" + "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u{E27F}" static let charConvStrCol80 : String = // INVERSE "\u{E140}\u{E141}\u{E142}\u{E143}\u{E144}\u{E145}\u{E146}\u{E147}\u{E148}\u{E149}\u{E14A}\u{E14B}\u{E14C}\u{E14D}\u{E14E}\u{E14F}\u{E150}\u{E151}\u{E152}\u{E153}\u{E154}\u{E155}\u{E156}\u{E157}\u{E158}\u{E159}\u{E15A}\u{E15B}\u{E15C}\u{E15D}\u{E15E}\u{E15F}\u{E120}\u{E121}\u{E122}\u{E123}\u{E124}\u{E125}\u{E126}\u{E127}\u{E128}\u{E129}\u{E12A}\u{E12B}\u{E12C}\u{E12D}\u{E12E}\u{E12F}\u{E130}\u{E131}\u{E132}\u{E133}\u{E134}\u{E135}\u{E136}\u{E137}\u{E138}\u{E139}\u{E13A}\u{E13B}\u{E13C}\u{E13D}\u{E13E}\u{E13F}" + // INVERSE 2 with small caps "\u{E140}\u{E141}\u{E142}\u{E143}\u{E144}\u{E145}\u{E146}\u{E147}\u{E148}\u{E149}\u{E14A}\u{E14B}\u{E14C}\u{E14D}\u{E14E}\u{E14F}\u{E150}\u{E151}\u{E152}\u{E153}\u{E154}\u{E155}\u{E156}\u{E157}\u{E158}\u{E159}\u{E15A}\u{E15B}\u{E15C}\u{E15D}\u{E15E}\u{E15F}\u{E160}\u{E161}\u{E162}\u{E163}\u{E164}\u{E165}\u{E166}\u{E167}\u{E168}\u{E169}\u{E16A}\u{E16B}\u{E16C}\u{E16D}\u{E16E}\u{E16F}\u{E170}\u{E171}\u{E172}\u{E173}\u{E174}\u{E175}\u{E176}\u{E177}\u{E178}\u{E179}\u{E17A}\u{E13B}\u{E13C}\u{E13D}\u{E13E}\u{E13F}" + "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_ !\"#$%&'()*+,-./0123456789:;<=>?" + "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u{E27F}" static let charConvTblFlashOn40 = Array( charConvStrFlashOn40 ) static let charConvTblFlashOff40 = Array( charConvStrFlashOff40 ) static let charConvTblCol80 = Array( charConvStrCol80 ) static var charConvTblFlashOn = charConvTblFlashOn40 static var charConvTblFlashOff = charConvTblFlashOff40 static var charConvTbl = charConvTblFlashOn // static var romFileName = "Apple2e_Enhanced.rom" static var romFileName = "Apple2e_32k.rom" // static var romFileName = "077-0019 Apple IIe Diagnostic Card - English.rom" static let textLineOfs : [Int] = [ 0x000, 0x080, 0x100, 0x180, 0x200, 0x280, 0x300, 0x380, 0x028, 0x0A8, 0x128, 0x1A8, 0x228, 0x2A8, 0x328, 0x3A8, 0x050, 0x0D0, 0x150, 0x1D0, 0x250, 0x2D0, 0x350, 0x3D0 ] func dialogOK(title: String, text: String) { let alert = NSAlert() alert.messageText = title alert.informativeText = text alert.alertStyle = .warning alert.addButton(withTitle: "OK") alert.runModal() // return alert.runModal() == .alertFirstButtonReturn } @objc func chk_woz_load(err : Int32) { switch (err) { case WOZ_ERR_OK: break case WOZ_ERR_FILE_NOT_FOUND: dialogOK(title: "Error Loading Disk Image", text: "File Not Found!") break case WOZ_ERR_NOT_WOZ_FILE: dialogOK(title: "Error Loading Disk Image", text: "File is not a WOZ image!") break case WOZ_ERR_BAD_CHUNK_HDR, WOZ_ERR_BAD_DATA: dialogOK(title: "Error Loading Disk Image", text: "Malformed WOZ image!") break default: dialogOK(title: "Error Loading Disk Image", text: "Unknown Error! (\(err))" ) break } } var workItem : DispatchWorkItem? = nil; @IBAction func PowerOn(_ sender: Any) { #if SCHEDULER_CVDISPLAYLINK CVDisplayLinkStop(displayLink!) #else upd.suspend() #endif cpuState = cpuState_inited; spkr_stopAll() //------------------------------------------------------------ // Animated Splash Screen fade out and (Text) Monitor fade in hires.isHidden = true lores.isHidden = true // displayField.alphaValue = 0 // displayField.isHidden = false textDisplayScroller.alphaValue = 0 textDisplayScroller.isHidden = false splashScreen.alphaValue = 1 splashScreen.isHidden = false DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { NSAnimationContext.runAnimationGroup({ (context) in context.duration = 1.0 // Use the value you want to animate to (NOT the starting value) self.textDisplayScroller.animator().alphaValue = 1 self.hires.animator().alphaValue = 1 self.lores.animator().alphaValue = 1 self.splashScreen.animator().alphaValue = 0 }, completionHandler:{ () -> Void in self.textDisplayScroller.alphaValue = 1 self.hires.alphaValue = 1 self.lores.alphaValue = 1 self.splashScreen.isHidden = true }) m6502_ColdReset( Bundle.main.resourcePath! + "/rom/", ViewController.romFileName ) cpuState = cpuState_running; #if SCHEDULER_CVDISPLAYLINK CVDisplayLinkStart(self.displayLink!) #else self.upd.resume() #endif if let debugger = DebuggerWindowController.shared { debugger.PauseButtonUpdate(needUpdateMainToolbar: false) } } //------------------------------------------------------------ #if SPEEDTEST if ( workItem != nil ) { workItem!.cancel(); workItem = nil; } else { workItem = DispatchWorkItem { DispatchQueue.global(qos: .userInteractive).async { // DispatchQueue.global(qos: .userInitiated).async { // DispatchQueue.global(qos: .background).async { tst6502() } } DispatchQueue.global().async(execute: workItem!); } #else #endif } @IBAction func PowerOff(_ sender: Any) { #if SCHEDULER_CVDISPLAYLINK CVDisplayLinkStop(displayLink!) #else upd.suspend() #endif cpuState = cpuState_inited; spkr_stopAll() //------------------------------------------------------------ // Animated Splash Screen fade out and (Text) Monitor fade in DispatchQueue.main.asyncAfter(deadline: .now() + 0.0) { self.splashScreen.alphaValue = 0 self.splashScreen.isHidden = false NSAnimationContext.runAnimationGroup({ (context) in context.duration = 0.5 // Use the value you want to animate to (NOT the starting value) self.textDisplayScroller.animator().alphaValue = 0 self.hires.animator().alphaValue = 0 self.lores.animator().alphaValue = 0 self.splashScreen.animator().alphaValue = 1 }, completionHandler:{ () -> Void in self.textDisplayScroller.alphaValue = 0 self.textDisplayScroller.isHidden = true self.splashScreen.isHidden = false self.hires.alphaValue = 0 self.lores.alphaValue = 0 self.hires.isHidden = true self.lores.isHidden = true self.splashScreen.isHidden = false }) } if let debugger = DebuggerWindowController.shared { debugger.PauseButtonUpdate(needUpdateMainToolbar: false) } // hires.isHidden = true // lores.isHidden = true // textDisplayScroller.alphaValue = 0 //// textDisplayScroller.isHidden = false // splashScreen.alphaValue = 1 // splashScreen.isHidden = false //------------------------------------------------------------ } func debuggerShowWindow() { if let debuggerWindowController = DebuggerWindowController.shared { DispatchQueue.main.async { debuggerWindowController.showWindow(self) } } } func debuggerRemoveHighlight() { if let debuggerViewController = DebuggerViewController.shared { debuggerViewController.remove_highlight(view: debuggerViewController.Disass_Display, line: debuggerViewController.highlighted_line_number) } } func debuggerPauseUpdate() { if let debuggerViewController = DebuggerViewController.shared { debuggerViewController.TrunDisassAddressPC(.on) debuggerViewController.remove_highlight(view: debuggerViewController.Disass_Display, line: debuggerViewController.highlighted_line_number) debuggerViewController.Update() } } func Resume() { debuggerRemoveHighlight() #if SCHEDULER_CVDISPLAYLINK CVDisplayLinkStart(displayLink!) #else upd.resume() #endif cpuState = cpuState_running DispatchQueue.main.async { self.view.window?.windowController?.showWindow(self) } if let debugger = DebuggerWindowController.shared { debugger.PauseButtonUpdate() } } func Pause() { #if SCHEDULER_CVDISPLAYLINK CVDisplayLinkStop(displayLink!) #else upd.suspend() #endif cpuState = cpuState_halted if let debugger = DebuggerWindowController.shared { debugger.PauseButtonUpdate() } debuggerPauseUpdate() } @IBAction func Pause(_ sender: Any) { switch ( cpuState ) { case cpuState_halted: Resume() break case cpuState_running: Pause() break default: // upd.suspend() // cpuState = cpuState_halted break } } @IBAction func Reset(_ sender: Any) { // m6502.interrupt = SOFTRESET; // let saved_frm_set = clk_6502_per_frm_set; // clk_6502_per_frm_set = 0 // clk_6502_per_frm_max = 0 // // wait for 1 ms to allow the simulation to halt // usleep(10000); softReset() // clk_6502_per_frm_set = saved_frm_set } static let textPage1Addr = 0x400 static let textPage2Addr = 0x800 static let textBufferSize = 0x400 // static only needed to be able to initialize things static let textLines = 24 static let textCols = 40 static let lineEndChars = 1 // these are needed to be able to easy access to these constants from instances let textLines = ViewController.textLines let textCols = ViewController.textCols let lineEndChars = ViewController.lineEndChars var frameCnt = 0 var flashInverted = false // let spaceChar : Character = "\u{E17F}" // let blockChar : Character = "\u{E07F}" // static let spaceChar : Character = " " // static let blockChar : Character = "β–‘" // static var flashingSpace : Character = " " let ramBufferPointer = UnsafeRawBufferPointer(start: MEM, count: 64 * 1024) static let textPage1Pointer = UnsafeRawBufferPointer(start: MEM + textPage1Addr, count: textBufferSize) static let textPage2Pointer = UnsafeRawBufferPointer(start: MEM + textPage2Addr, count: textBufferSize) static let textIntBufferPointer = UnsafeRawBufferPointer(start: RAM + textPage1Addr, count: textBufferSize) static let textAuxBufferPointer = UnsafeRawBufferPointer(start: AUX + textPage1Addr, count: textBufferSize) static let textPageShadowBuffer = UnsafeMutableRawBufferPointer.allocate(byteCount: textBufferSize, alignment: 1) // TODO: Render text screen in native C // static let textScreen = UnsafeMutableRawPointer(mutating: testText) var textBufferPointer = textPage1Pointer static let textArraySize = textLines * (textCols + lineEndChars) + textCols * 2 let txtClear = [Character](repeating: " ", count: textArraySize * 2) var unicodeTextArray = [Character](repeating: " ", count: textArraySize * 2) var s = String() func HexDump() { var txt : String = "" for y in 0...textLines - 1 { txt += String(format: "%04X: ", y * 16) for x in 0...15 { let byte = ramBufferPointer[ y * 16 + x ] let chr = String(format: "%02X ", byte) txt += chr } txt += "\n" } DispatchQueue.main.async { self.textDisplay.string = txt self.speedometer.stringValue = String(format: "%0.3lf MHz", mhz); } } // AppleScript Keycodes let leftArrowKey = 123 let rightArrowKey = 124 let upArrowKey = 126 let downArrowKey = 125 let F4FunctionKey = 118 let F5FunctionKey = 96 let F6FunctionKey = 97 let F7FunctionKey = 98 let F8FunctionKey = 100 override var acceptsFirstResponder: Bool { get { return true } } func SelectAll() { // textDisplayScroller.currentEditor()?.selectAll(nil) // displayField.selectText(nil) textDisplay.setSelectedRange(NSRange()) } func Copy() { let pasteBoard = NSPasteboard.general pasteBoard.clearContents() // TODO: Find a better way to avoid index out of range error when the entire text area is selected let string = textDisplay.string + " " let selectedRange = textDisplay.selectedRange() if selectedRange != NSRange() { let startIndex = string.index(string.startIndex, offsetBy: selectedRange.lowerBound) let endIndex = string.index(string.startIndex, offsetBy: selectedRange.upperBound) let selectedString = string[startIndex.. NSScreen? { let mouseLocation = NSEvent.mouseLocation let screens = NSScreen.screens let screenWithMouse = (screens.first { NSMouseInRect(mouseLocation, $0.frame, false) }) return screenWithMouse } func convertPoint(toCG point : NSPoint) -> CGPoint { /// Cocoa and Core Graphics (a.k.a. Quartz) use different coordinate systems. In Cocoa, the origin is at the lower left of the primary screen and y increases as you go up. In Core Graphics, the origin is at the top left of the primary screen and y increases as you go down. /// Need to convert coordinates from Cocoa to Core Graphics var cgpoint = view.window!.convertPoint(toScreen: point) if let screen = getScreenWithMouse() { cgpoint.y = NSHeight(screen.frame) - cgpoint.y; } return cgpoint } override func mouseMoved(with event: NSEvent) { // print(#function) var location = event.locationInWindow // displayOrigin = textDisplayScroller.frame.origin. // print("mx:", location.x, " my:", location.y) var mouseCursorNeedsReplace = false if location.x < 8 { mouseCursorNeedsReplace = true location.x = 8 } if location.x > textDisplay.frame.width - 8 { mouseCursorNeedsReplace = true location.x = textDisplay.frame.width - 8 } if location.y < 8 { mouseCursorNeedsReplace = true location.y = 8 } if location.y > textDisplay.frame.height - 8 { mouseCursorNeedsReplace = true location.y = textDisplay.frame.height - 8 } mouseCursor(hide: Mouse2Joystick) if ( Mouse2Joystick ) { if mouseCursorNeedsReplace { CGWarpMouseCursorPosition(convertPoint(toCG: location)) } pdl_prevarr[0] = pdl_valarr[0] pdl_valarr[0] = Double(location.x / (textDisplay.frame.width) ) pdl_diffarr[0] = pdl_valarr[0] - pdl_prevarr[0] pdl_prevarr[1] = pdl_valarr[1] pdl_valarr[1] = 1 - Double(location.y / (textDisplay.frame.height) ) pdl_diffarr[1] = pdl_valarr[1] - pdl_prevarr[1] } if ( MouseInterface ) { pdl_prevarr[2] = pdl_valarr[2] pdl_valarr[2] = Double(location.x / (textDisplay.frame.width) ) pdl_diffarr[2] = pdl_valarr[2] - pdl_prevarr[2] pdl_prevarr[3] = pdl_valarr[3] pdl_valarr[3] = 1 - Double(location.y / (textDisplay.frame.height) ) pdl_diffarr[3] = pdl_valarr[3] - pdl_prevarr[3] } } var savedVideoMode = videoMode_t.init() override func keyDown(with event: NSEvent) { m6502.ecoSpindown = ecoSpindown; if ( cpuMode == cpuMode_eco ) { cpuState = cpuState_running; #if SCHEDULER_CVDISPLAYLINK CVDisplayLinkStart(displayLink!) #else upd.resume() #endif } // print("keyDown") // switch event.modifierFlags.intersection(.deviceIndependentFlagsMask) { // case [.command] where event.characters == "l", // [.command, .shift] where event.characters == "l": // print("command-l or command-shift-l") // default: // break // } // print( "key = " + (event.charactersIgnoringModifiers ?? "")) // print( "\ncharacter = " + (event.characters ?? "")) if event.modifierFlags.contains(.command) { // .shift, .option, .control ... if let chars = event.charactersIgnoringModifiers { switch chars { case "a": // print("CMD + A") SelectAll() return // to avoid deselect text case "c": // print("CMD + C") Copy() case "v": // print("CMD + V") Paste() default: super.keyDown(with: event) } } } else { #if FUNCTIONTEST #else let keyCode = Int(event.keyCode) switch keyCode { case leftArrowKey: // print("LEFT"); if ( Keyboard2Joystick ) { // Keyboard 2 JoyStick (Game Controller / Paddle) pdl_valarr[0] = 0 } kbdInput(0x88) case rightArrowKey: // print("RIGHT") // Keyboard 2 JoyStick (Game Controller / Paddle) if ( Keyboard2Joystick ) { pdl_valarr[0] = 1 } kbdInput(0x95) case downArrowKey: // print("DOWN") // Keyboard 2 JoyStick (Game Controller / Paddle) if ( Keyboard2Joystick ) { pdl_valarr[1] = 1 } else { kbdInput(0x8A) } case upArrowKey: // print("UP") // Keyboard 2 JoyStick (Game Controller / Paddle) if ( Keyboard2Joystick ) { pdl_valarr[1] = 0 } else { kbdInput(0x8B) } case F4FunctionKey: // if let debugger = DebuggerWindowController.current { // debugger.Continue() // } Resume() case F5FunctionKey: // if let debugger = DebuggerWindowController.current { // debugger.Pause() // } Pause() case F6FunctionKey: if let debugger = DebuggerWindowController.shared { debugger.Step_Over(event) } case F7FunctionKey: if let debugger = DebuggerWindowController.shared { debugger.Step_In(event) } case F8FunctionKey: if let debugger = DebuggerWindowController.shared { debugger.Step_Out(event) } default: // print("keycode: %d", keyCode) if let chars = event.characters { let char = chars.uppercased()[chars.startIndex] if let ascii = char.asciiValue { kbdInput(ascii) } } } #endif } // displayField.currentEditor()?.selectedRange = NSMakeRange(0, 0) textDisplay.setSelectedRange(NSRange()) } override func keyUp(with event: NSEvent) { // print("keyUp") // switch event.modifierFlags.intersection(.deviceIndependentFlagsMask) { // case [.command] where event.characters == "l", // [.command, .shift] where event.characters == "l": // print("command-l or command-shift-l") // default: // break // } // print( "key = " + (event.charactersIgnoringModifiers ?? "")) // print( "\ncharacter = " + (event.characters ?? "")) #if FUNCTIONTEST #else let keyCode = Int(event.keyCode) switch keyCode { case leftArrowKey: // kbdInput(0x08) // setIO(0xC064, 127); // print("left") // Keyboard 2 JoyStick (Game Controller / Paddle) pdl_valarr[0] = 0.5 case rightArrowKey: // kbdInput(0x15) // setIO(0xC064, 128); // print("right") // Keyboard 2 JoyStick (Game Controller / Paddle) pdl_valarr[0] = 0.5 case downArrowKey: // kbdInput(0x0B) // setIO(0xC065, 127); // print("down") // Keyboard 2 JoyStick (Game Controller / Paddle) pdl_valarr[1] = 0.5 case upArrowKey: // kbdInput(0x0A) // setIO(0xC065, 128); // print("up") // Keyboard 2 JoyStick (Game Controller / Paddle) pdl_valarr[1] = 0.5 default: // print("keycode: %d", keyCode) // if let chars = event.characters { // let char = chars.uppercased()[chars.startIndex] // if let ascii = char.asciiValue { // kbdInput(ascii) // } // } break } #endif kbdUp() } override func flagsChanged(with event: NSEvent) { let flags = event.modifierFlags.intersection(.deviceIndependentFlagsMask) switch flags { case [.shift]: setIO(0xC061, 0) setIO(0xC062, 0) setIO(0xC063, 0) // inverted (bit 7: not pressed) // print("shift key is pressed") // case [.control]: // print("control key is pressed") case [.option] : setIO(0xC061, 0) setIO(0xC062, 1 << 7) setIO(0xC063, 1 << 7) // inverted (bit 7: not pressed) // print("option key is pressed") case [.command]: // print("Command key is pressed") setIO(0xC061, 1 << 7) setIO(0xC062, 0) setIO(0xC063, 1 << 7) // inverted (bit 7: not pressed) // case [.control, .shift]: // print("control-shift keys are pressed") case [.option, .shift]: setIO(0xC061, 1 << 7) setIO(0xC062, 0) setIO(0xC063, 0) // inverted (bit 7: not pressed) // print("option-shift keys are pressed") case [.command, .shift]: setIO(0xC061, 1 << 7) setIO(0xC062, 0) setIO(0xC063, 0) // inverted (bit 7: not pressed) // print("command-shift keys are pressed") // case [.control, .option]: // print("control-option keys are pressed") // case [.control, .command]: // print("control-command keys are pressed") case [.option, .command]: setIO(0xC061, 1 << 7) setIO(0xC062, 1 << 7) setIO(0xC063, 1 << 7) // inverted (bit 7: not pressed) // print("option-command keys are pressed") // case [.shift, .control, .option]: // print("shift-control-option keys are pressed") // case [.shift, .control, .command]: // print("shift-control-command keys are pressed") // case [.control, .option, .command]: // print("control-option-command keys are pressed") case [.shift, .command, .option]: setIO(0xC061, 1 << 7) setIO(0xC062, 1 << 7) setIO(0xC063, 0); // inverted (bit 7: not pressed) // print("shift-command-option keys are pressed") // case [.shift, .control, .option, .command]: // print("shift-control-option-command keys are pressed") // case [.function]: // print("function key is pressed") // case [.capsLock]: // print("capsLock key is pressed") case [.control, .command, .option]: Mouse2Joystick = !Mouse2Joystick mouseCursor(hide: Mouse2Joystick) ToolBarController.current?.MouseToJoystickMenuItem.state = Mouse2Joystick ? .on : .off default: setIO(0xC061, 0) setIO(0xC062, 0) setIO(0xC063, 1 << 7) // inverted (bit 7: not pressed) // print("no modifier keys are pressed") } } var was = 0; var currentVideoMode = videoMode var lastFrameTime = CACurrentMediaTime() as Double var frameCounter : UInt32 = 0 var clkCounter : Double = 0 var shadowTxt : String = "" var unicodeTextString : String = "" func Flash() { self.frameCnt += 1 if ( self.frameCnt == fps / video_fps_divider / 2 ) { if !flashInverted { ViewController.charConvTbl = ViewController.charConvTblFlashOn flashInverted = true } } else if ( self.frameCnt >= fps / video_fps_divider ) { self.frameCnt = 0 if flashInverted { ViewController.charConvTbl = ViewController.charConvTblFlashOff flashInverted = false } } } var textNeedRender = false func RenderText() { Flash() textNeedRender = false var fromLines = 0 var toLines = textLines if videoMode.text == 0 { if videoMode.mixed == 1 { fromLines = toLines - 4 } else { toLines = 0 } } unicodeTextArray = NSArray(array: txtClear, copyItems: true) as! [Character] // render an empty space to eiminate displaying text portion of the screen covered by graphics let charDisposition = videoMode.col80 == 0 ? 1 : 2 for y in 0 ..< fromLines { unicodeTextArray[ y * (textCols * charDisposition + lineEndChars) + textCols * charDisposition] = "\n" } // 40 col if videoMode.col80 == 0 { if MEMcfg.txt_page_2 == 0 { if (MEMcfg.RD_AUX_MEM == 0) { textBufferPointer = ViewController.textPage1Pointer } else { textBufferPointer = ViewController.textIntBufferPointer } } else { textBufferPointer = ViewController.textPage2Pointer } if textBufferPointer.elementsEqual(ViewController.textPageShadowBuffer) { } else { ViewController.textPage1Pointer.copyBytes(to: ViewController.textPageShadowBuffer) textNeedRender = true // render the rest of the text screen for y in fromLines ..< toLines { for x in 0 ..< textCols { let byte = textBufferPointer[ ViewController.textLineOfs[y] + x ] let idx = Int(byte); let chr = ViewController.charConvTbl[idx] unicodeTextArray[ y * (textCols + lineEndChars) + x ] = chr } unicodeTextArray[ y * (textCols + lineEndChars) + textCols ] = "\n" } unicodeTextString = String(unicodeTextArray) } } // 80 col else { let auxPage = ( MEMcfg.is_80STORE == 1 ) && ( MEMcfg.txt_page_2 == 1 ) let textIntBuffer = auxPage ? ViewController.textIntBufferPointer : ViewController.textPage1Pointer let textAuxBuffer = auxPage ? ViewController.textPage1Pointer : ViewController.textAuxBufferPointer // render the rest of the text screen for y in fromLines ..< toLines { for x in 0 ..< textCols { let byte = textIntBuffer[ ViewController.textLineOfs[y] + x ] let idx = Int(byte); let chr = ViewController.charConvTbl[idx] unicodeTextArray[ y * (textCols * 2 + lineEndChars) + x * 2 + 1] = chr let byte2 = textAuxBuffer[ ViewController.textLineOfs[y] + x ] let idx2 = Int(byte2); let chr2 = ViewController.charConvTbl[idx2] unicodeTextArray[ y * (textCols * 2 + lineEndChars) + x * 2] = chr2 } unicodeTextArray[ y * (textCols * 2 + lineEndChars) + textCols * 2] = "\n" } unicodeTextString = String(unicodeTextArray) } } func SetSplashScreenFont() { for view in splashScreen.subviews { if view is NSTextField { let textField = view as! NSTextField if let fontSize = textField.font?.pointSize { textField.font = NSFont(name: "PrintChar21", size: fontSize) } } } // Set Apple ][ font if let fontSize = textDisplay.font?.pointSize { textDisplay.font = NSFont(name: "PrintChar21", size: fontSize) ViewController.charConvTblFlashOn = ViewController.charConvTblFlashOn40 ViewController.charConvTblFlashOff = ViewController.charConvTblFlashOff40 } } func SetCol40() { // Set Apple ][ font if let fontSize = textDisplay.font?.pointSize { textDisplay.font = NSFont(name: "PrintChar21", size: fontSize) ViewController.charConvTblFlashOn = ViewController.charConvTblFlashOn40 ViewController.charConvTblFlashOff = ViewController.charConvTblFlashOff40 } } func SetCol80() { // Set Apple ][ font if let fontSize = textDisplay.font?.pointSize { textDisplay.font = NSFont(name: "PRNumber3", size: fontSize) ViewController.charConvTblFlashOn = ViewController.charConvTblCol80 ViewController.charConvTblFlashOff = ViewController.charConvTblCol80 } } func UpdateText() { // TODO: Render text Screen in native C // txt = String(bytesNoCopy: ViewController.textScreen!, length: 10, encoding: .ascii, freeWhenDone: false) ?? "HMM" if videoMode.col80 != currentVideoMode.col80 { currentVideoMode.col80 = videoMode.col80 if videoMode.col80 == 1 { SetCol80() } else { SetCol40() } } if textNeedRender || shadowTxt != unicodeTextString { shadowTxt = unicodeTextString let selectedRange = textDisplay.selectedRange() // DispatchQueue.main.async { [self] in textDisplay.string = unicodeTextString textDisplay.setSelectedRange(selectedRange) // } // let bold14 = NSFont.boldSystemFont(ofSize: 14.0) // let textColor = NSColor.red // let attribs = [NSAttributedString.Key.font:bold14,NSAttributedString.Key.foregroundColor:textColor,NSAttributedString.Key.paragraphStyle:textParagraph] // let textParagraph = NSMutableParagraphStyle() // textParagraph.lineSpacing = 0 // textParagraph.minimumLineHeight = 32.0 // textParagraph.maximumLineHeight = 32.0 // // let attribs = [NSAttributedString.Key.paragraphStyle: textParagraph] // let attrString:NSAttributedString = NSAttributedString.init(string: unicodeTextString, attributes: attribs) // display.attributedStringValue = attrString } // display.stringValue = "testing\nit\nout" } func UpdateCPUspeed() { // DispatchQueue.main.async { [self] in // under ~1.5 MHz -- 3 decimals to be able to display 1.023 MHz if ( (mhz < 1.4) && (mhz != floor(mhz)) ) { speedometer.stringValue = String(format: "%0.3lf MHz", mhz); } // under ~100 MHz -- 1 decimal else if (mhz < 95) { speedometer.stringValue = String(format: "%0.1lf MHz", mhz); } // over ~1000 MHz -- 1 decimal GHz else if (mhz > 950) { speedometer.stringValue = String(format: "%0.1lf GHz", mhz / 1000); } // hundreds -- no decimals else { speedometer.stringValue = String(format: "%0.0lf MHz", mhz); } // } } func RenderGraphics() { // only refresh graphics view when needed (aka not in text mode) if ( videoMode.text == 0 ) { if ( videoMode.hires == 0 ) { // when we change video mode, buffer needs to be cleared to avoid artifacts if ( savedVideoMode.text == 1 ) || ( savedVideoMode.mixed != videoMode.mixed ) || ( savedVideoMode.hires != videoMode.hires ) { lores.clearScreen() lores.isHidden = false hires.isHidden = true unicodeTextString = String(unicodeTextArray) } lores.Render() } else { // when we change video mode, buffer needs to be cleared to avoid artifacts if ( savedVideoMode.text == 1 ) || ( savedVideoMode.mixed != videoMode.mixed ) || ( savedVideoMode.hires != videoMode.hires ) { hires.clearScreen() hires.isHidden = false lores.isHidden = true unicodeTextString = String(unicodeTextArray) } hires.Render() } } else if ( savedVideoMode.text == 0 ) { // we just switched from grahics to text lores.isHidden = true hires.isHidden = true unicodeTextString = String(unicodeTextArray) } savedVideoMode = videoMode } func Render() { self.RenderText() // Rendering is happening in the main thread, which has two implications: // 1. We can update UI elements // 2. it is independent of the simulation, de that is running in the background thread while we are busy with rendering... // DispatchQueue.global(qos: .userInitiated).async { DispatchQueue.main.async { self.UpdateText() self.UpdateCPUspeed() #if HIRES self.RenderGraphics() #endif // HIRES } } override func mouseDown(with event: NSEvent) { // print(#function) switch event.modifierFlags.intersection(.deviceIndependentFlagsMask) { case [.control, .command, .option]: Mouse2Joystick = !Mouse2Joystick mouseCursor(hide: Mouse2Joystick) ToolBarController.current?.MouseToJoystickMenuItem.state = Mouse2Joystick ? .on : .off default: break } if ( Mouse2Joystick ) { setIO(0xC061, 1 << 7) } } override func mouseUp(with event: NSEvent) { // print(#function) if ( Mouse2Joystick ) { setIO(0xC061, 0) } } override func rightMouseDown(with event: NSEvent) { // print(#function) if ( Mouse2Joystick ) { setIO(0xC062, 1 << 7) } } override func rightMouseUp(with event: NSEvent) { // print(#function) if ( Mouse2Joystick ) { setIO(0xC062, 0) } } override func otherMouseDown(with event: NSEvent) { // print(#function) if ( Mouse2Joystick ) { setIO(0xC063, 0) // inverted (bit 7: 0 = pressed) } } override func otherMouseUp(with event: NSEvent) { // print(#function) if ( Mouse2Joystick ) { setIO(0xC063, 1 << 7) // inverted (bit 7: 1 = not pressed) } } func diskButtonUpdate() { DispatchQueue.main.async { if ( self.frameCounter % DEF_DRV_LED_DIV == 0 ) { // Disk Motor LED if spkr_is_disk_motor_playing() { if self.disk1_led.isHidden { self.disk1_led.isHidden = false } } else { if !self.disk1_led.isHidden { self.disk1_led.isHidden = true } } // Disk Loaded if woz_is_loaded() > 0 { if self.disk1_closed.isHidden { self.disk1_closed.isHidden = false } } else { if !self.disk1_closed.isHidden { self.disk1_closed.isHidden = true } } } } } func debugBreak() { Pause() spkr_play_disk_motor_time = 0 spkr_stopAll() // TODO: This should be in Debugger! debuggerPauseUpdate() debuggerShowWindow() } let UpdateSemaphore = DispatchSemaphore(value: 1) func Update() { // clk_6502_per_frm_max = 0 if UpdateSemaphore.wait(timeout: .now() + 0.001) == .timedOut { // get back here next time... return } diskButtonUpdate() switch cpuState { case cpuState_running: clkCounter += Double(m6502.clkfrm) // we start a new frame from here, so CPU is running even while rendering // m6502.clkfrm = 0 frameCounter += 1 if ( frameCounter % fps == 0 ) { let currentTime = CACurrentMediaTime() as Double let elpasedTime = currentTime - lastFrameTime lastFrameTime = currentTime mhz = Double( clkCounter ) / (elpasedTime * M); clkCounter = 0 } #if SPEEDTEST #else // poll input devices like mouse and joystick // Input() // run some code // cpuState = cpuState_executing // DispatchQueue.global(qos: .userInitiated).async { if m6502.debugger.on { m6502_Debug() switch m6502.interrupt { case HALT: debugBreak() case BREAK: // BRK instruction debugBreak() case BREAKPOINT: // CPU halted because of a breakpoint debugBreak() case BREAKRDMEM: // CPU halted because of a breakpoint debugBreak() case BREAKWRMEM: // CPU halted because of a breakpoint debugBreak() case RET: if m6502.debugger.mask.ret == 1 { // Step_Out / Step_Over if m6502.PC >= m6502.debugger.SP { debugBreak() } } case INV: // invalid instruction debugBreak() default: break } // clear iterrupt m6502.interrupt = NO_INT } else { // DispatchQueue.global(qos: .userInitiated).async { m6502_Run() // } // cpuState = cpuState_running } // video rendering if ( frameCounter % video_fps_divider == 0 ) { Render() } // TODO: This should be in Debugger! if let debugger = DebuggerViewController.shared { debugger.Update() } #endif break case cpuState_executing: // prevent running more instances per session // setCPUClockSpeed(freq: MHz_6502 - 1) break case cpuState_halting: cpuState = cpuState_halted // last video rendering before halt Render() break case cpuState_halted: #if SCHEDULER_CVDISPLAYLINK CVDisplayLinkStop(displayLink!) #else upd.suspend() #endif break default: break } // ok, from now you can update again UpdateSemaphore.signal() } // func FromBuf(ptr: UnsafeMutablePointer, length len: Int) -> String? { // // convert the bytes using the UTF8 encoding // if let theString = NSString(bytes: ptr, length: len, encoding: NSUTF8StringEncoding) { // return theString as String // } else { // return nil // the bytes aren't valid UTF8 // } // } #if SCHEDULER_CVDISPLAYLINK #else var upd = RepeatingTimer(timeInterval: 1) func newUpdateTimer( timeInterval : Double ) { upd.kill() upd = RepeatingTimer(timeInterval: timeInterval) upd.eventHandler = { self.Update() } upd.resume() } #endif // Kelvin Sherlock's fix to avoid uninstalled font problems override func awakeFromNib() { // self.display.font = NSFont(name: "PrintChar21", size: 32) } required init?(coder: NSCoder) { // print(#function) super.init(coder: coder) ViewController.shared = self } // func render() { // // var x = vertexData[2 * 3 + 0] // x -= 0.01 // if x < -1 { // x = 1 // } // vertexData[2 * 3 + 0] = x // // guard let drawable = metalLayer?.nextDrawable() else { return } // let renderPassDescriptor = MTLRenderPassDescriptor() // renderPassDescriptor.colorAttachments[0].texture = drawable.texture // renderPassDescriptor.colorAttachments[0].loadAction = .clear // renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColor( // red: 0.0, // green: 104.0/255.0, // blue: 55.0/255.0, // alpha: 0.3) // // if let commandBuffer = commandQueue.makeCommandBuffer() { // if let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor) { // renderEncoder.setRenderPipelineState(pipelineState) // let dataSize = vertexData.count * MemoryLayout.size(ofValue: vertexData[0]) // 1 // vertexBuffer = device.makeBuffer(bytes: vertexData, length: dataSize, options: []) // 2 // renderEncoder.setVertexBuffer(vertexBuffer, offset: 0, index: 0) // renderEncoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 3, instanceCount: 1) // renderEncoder.endEncoding() // commandBuffer.present(drawable) // commandBuffer.commit() // } // } // } // // // var vertexData: [Float] = [ // 0.0, 1.0, 0.0, // -1.0, -1.0, 0.0, // 1.0, -1.0, 0.0 // ] #if SCHEDULER_CVDISPLAYLINK // The callback function is called everytime CVDisplayLink says its time to get a new frame. let displayLinkOutputCallback : CVDisplayLinkOutputCallback = { (displayLink: CVDisplayLink, _ inNow: UnsafePointer, _ inOutputTime: UnsafePointer, _ flagsIn: CVOptionFlags, _ flagsOut: UnsafeMutablePointer, _ vController: UnsafeMutableRawPointer?) -> CVReturn in /* The displayLinkContext is CVDisplayLink's parameter definition of the view in which we are working. In order to access the methods of a given view we need to specify what kind of view it is as right now the UnsafeMutablePointer just means we have a pointer to "something". To cast the pointer such that the compiler at runtime can access the methods associated with our SwiftOpenGLView, we use an unsafeBitCast. The definition of which states, "Returns the the bits of x, interpreted as having type U." We may then call any of that view's methods. Here we call drawView() which we draw a frame for rendering. */ // unsafeBitCast(displayLinkContext, SwiftOpenGLView.self).renderFrame() /// This can be a precise FPS updater so MHz would be dead perfect pitch -- however, it makes things worse // let now : CVTimeStamp = inNow.pointee //// print( "RateScaler:", now.rateScalar ) // if (mySelf.last_frame_time != 0) { // let videoTimeDiff = now.videoTime - mySelf.last_frame_time // let currentFPS = Double(now.videoTimeScale) / Double(videoTimeDiff) // // fps = currentFPS // // mySelf.setCPUClockSpeed(freq: MHz_6502) // clk_6502_per_frm = UInt64( MHz_6502 * M / currentFPS ) // clk_6502_per_frm_set = clk_6502_per_frm // // } // mySelf.last_frame_time = now.videoTime; let mySelf = Unmanaged.fromOpaque(vController!).takeUnretainedValue() mySelf.Update(); // We are going to assume that everything went well for this mock up, and pass success as the CVReturn return kCVReturnSuccess } // sets a callback at every screen refresh (normally 60Hz) func setupDisplayLink() { // Grab the a link to the active displays, set the callback defined above, and start the link. /* An alternative to a nested function is a global function or a closure passed as the argument--a local function (i.e. a function defined within the class) is NOT allowed. */ // The UnsafeMutablePointer(unsafeAddressOf(self)) passes a pointer to the instance of our class. CVDisplayLinkCreateWithActiveCGDisplays(&displayLink) let unsafeSelf: UnsafeMutableRawPointer = Unmanaged.passUnretained(self).toOpaque() CVDisplayLinkSetOutputCallback(displayLink!, displayLinkOutputCallback, unsafeSelf) // CVDisplayLinkStart(displayLink!) } #endif @IBOutlet weak var disk1_led: NSImageView! @IBOutlet weak var disk2_led: NSImageView! @IBOutlet weak var disk1_closed: NSImageView! @IBOutlet weak var disk2_closed: NSImageView! var keyDownMonitor : Any? var keyUpMonitor : Any? func keyEventsOn() { // NSEvent.removeMonitor(NSEvent.EventType.flagsChanged) // NSEvent.addLocalMonitorForEvents(matching: .flagsChanged) { // self.flagsChanged(with: $0) // return $0 // } if let event = keyDownMonitor { NSEvent.removeMonitor(event) keyDownMonitor = nil } keyDownMonitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown) { // print("keyDown event") self.keyDown(with: $0) return nil } if let event = keyUpMonitor { NSEvent.removeMonitor(event) keyUpMonitor = nil } keyUpMonitor = NSEvent.addLocalMonitorForEvents(matching: .keyUp) { // print("keyUp event") self.keyUp(with: $0) return nil } } func keyEventsOff() { // NSEvent.removeMonitor(NSEvent.EventType.flagsChanged) if let event = keyDownMonitor { NSEvent.removeMonitor(event) keyDownMonitor = nil } if let event = keyUpMonitor { NSEvent.removeMonitor(event) keyUpMonitor = nil } } override func viewDidLoad() { super.viewDidLoad() // if let image = Disk1_open_on_img { // Disk1_ButtonCell.alternateImage = image // NSLog("Disk1_ButtonCell:%@", Disk1_ButtonCell) // } // let layer = CALayer() // hires.layer = layer // hires.wantsLayer = true // device = MTLCreateSystemDefaultDevice() // metalLayer = CAMetalLayer() // 1 // metalLayer.device = device // 2 // metalLayer.pixelFormat = .bgra8Unorm // 3 // metalLayer.framebufferOnly = true // 4 // metalLayer.frame = hires.layer!.frame // 5 // hires.layer!.addSublayer(metalLayer) // 6 // // let dataSize = vertexData.count * MemoryLayout.size(ofValue: vertexData[0]) // 1 // vertexBuffer = device.makeBuffer(bytes: vertexData, length: dataSize, options: []) // 2 // // // 1 // let defaultLibrary = device.makeDefaultLibrary()! // let fragmentProgram = defaultLibrary.makeFunction(name: "basic_fragment") // let vertexProgram = defaultLibrary.makeFunction(name: "basic_vertex") // // // 2 // let pipelineStateDescriptor = MTLRenderPipelineDescriptor() // pipelineStateDescriptor.vertexFunction = vertexProgram // pipelineStateDescriptor.fragmentFunction = fragmentProgram // pipelineStateDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm // // // 3 // pipelineState = try! device.makeRenderPipelineState(descriptor: pipelineStateDescriptor) // // commandQueue = device.makeCommandQueue() // timer = CADisplayLink(target: self, selector: #selector(gameloop)) // timer.add(to: RunLoop.main, forMode: .default) // Set Apple ][ font SetCol40() SetSplashScreenFont() openLog() hires.clearScreen(); spkr_load_sfx( Bundle.main.resourcePath! + "/sfx" ) let woz_err = woz_loadFile( Bundle.main.resourcePath! + "/dsk/Apple DOS 3.3 January 1983.woz" ) chk_woz_load(err: woz_err) woz_flags.image_file_readonly = 1 //view.frame = CGRect(origin: CGPoint(), size: NSScreen.main!.visibleFrame.size) // createHiRes() self.textDisplayScroller.scaleUnitSquare(to: NSSize(width: 1, height: 1)) keyEventsOn() // displayField.maximumNumberOfLines = textLines // displayField.preferredMaxLayoutWidth = displayField.frame.width // DispatchQueue.main.asyncAfter(deadline: .now() + 1/fps, execute: { // self.update() // }) // #if FUNCTIONTEST // #else // DispatchQueue.global(qos: .background).async { // self.update() // } // upd.eventHandler = { // self.Update() // } // upd.resume() #if SCHEDULER_CVDISPLAYLINK // Grab the a link to the active displays, set the callback defined above, and start the link. /* An alternative to a nested function is a global function or a closure passed as the argument--a local function (i.e. a function defined within the class) is NOT allowed. */ // The UnsafeMutablePointer(unsafeAddressOf(self)) passes a pointer to the instance of our class. CVDisplayLinkCreateWithActiveCGDisplays(&displayLink) let unsafeSelf: UnsafeMutableRawPointer = Unmanaged.passUnretained(self).toOpaque() CVDisplayLinkSetOutputCallback(displayLink!, displayLinkOutputCallback, unsafeSelf) CVDisplayLinkStart(displayLink!) #else newUpdateTimer( timeInterval: 1 / Double(fps) ) #endif // soundGapSlider.integerValue = Int(spkr_extra_buf) // ledingInitEdgeLabel.title = "ILE: " + String( SPKR_INITIAL_LEADING_EDGE ) // initialLeadEdgeSlider.floatValue = SPKR_INITIAL_LEADING_EDGE // leadingEdgeLabel.title = "LE: " + String( SPKR_FADE_LEADING_EDGE ) // leadEdgeSlider.floatValue = SPKR_FADE_LEADING_EDGE // trailingInitEdgeLabel.title = "ITE: " + String( SPKR_INITIAL_TRAILING_EDGE ) // initialTailEdgeSlider.floatValue = SPKR_INITIAL_TRAILING_EDGE // trailingEdgeLabel.title = "TE: " + String( SPKR_FADE_TRAILING_EDGE ) // tailEdgeSlider.floatValue = SPKR_FADE_TRAILING_EDGE // // BUGFIX: I am not sure why but if I do not adjust the frame and bounds size // // couple of times, Cocoa miscalculates them // var size = MonitorView.textViewBounds // size.width /= 2 // size.height /= 2 // textDisplay.setFrameSize(size) // textDisplay.setBoundsSize(size) // // size.width += 136 + 11 * 2 // size.height += 64 + 11 * 2 // view.setFrameSize(size) // view.setBoundsSize(size) } override func viewDidAppear() { // displayField.currentEditor()?.selectedRange = NSMakeRange(0, 0) // self.displayField.window?.makeFirstResponder(self) textDisplay.setSelectedRange(NSRange()) textDisplay.window?.makeFirstResponder(self) DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { self.monitorView.adjustTextDisplaySize() } } func setSpkrExtrabuf( freq : Double ) { // TODO: Probably this is not the best way to deal with the problem: To make sound continous independent of FPS and Freq // spkr_extra_buf = Int32( 780 / fps ) switch freq { case 0.25: spkr_extra_buf = -65 break case 0.5: spkr_extra_buf = -140 break case 1.5: spkr_extra_buf = 175 break case 2.0: // spkr_extra_buf = Int32( Double(spkr_extra_buf) * 2.961538461538462 ) // normally it should come up as 77, but this way it is calculated with FPS // spkr_extra_buf = 20 spkr_extra_buf = 195 // 88 break case 2.8: spkr_extra_buf = 65 // 185 break case 4.0: // spkr_extra_buf = Int32( Double(spkr_extra_buf) * 1.346153846153846 ) // normally it should come up as 35, but this way it is calculated with FPS // spkr_extra_buf = 45 spkr_extra_buf = 25 // 90 // 80 // 20 break default: // spkr_extra_buf = Int32( 780 / fps ) // normally it should come up as 26, but this way it is calculated with FPS spkr_extra_buf = 0 // 26 break } soundGapLabel.title = String( spkr_extra_buf ) soundGapSlider.integerValue = Int(spkr_extra_buf) } func setCPUClockSpeed( freq : Double ) { spkr_stopAll(); MHz_6502 = freq clk_6502_per_frm = UInt32( MHz_6502 * M / Double(fps) ) clk_6502_per_frm_set = clk_6502_per_frm spkr_extra_buf = 0 // setSpkrExtrabuf(freq: freq) } @IBOutlet weak var soundGapLabel: NSTextFieldCell! @IBAction func extraBuf(_ sender: NSSlider) { spkr_extra_buf = sender.intValue soundGapLabel.title = String( spkr_extra_buf ) } @IBOutlet weak var ledingInitEdgeLabel: NSTextFieldCell! @IBAction func leadingInitEdgeSelected(_ sender: NSSlider) { SPKR_INITIAL_LEADING_EDGE = sender.floatValue ledingInitEdgeLabel.title = "ILE: " + String( SPKR_INITIAL_LEADING_EDGE ) } @IBOutlet weak var leadingEdgeLabel: NSTextFieldCell! @IBAction func leadingEdgeSelected(_ sender: NSSlider) { SPKR_FADE_LEADING_EDGE = sender.floatValue leadingEdgeLabel.title = "LE: " + String( SPKR_FADE_LEADING_EDGE ) } @IBOutlet weak var trailingInitEdgeLabel: NSTextFieldCell! @IBAction func trailingInitEdgeSelected(_ sender: NSSlider) { SPKR_INITIAL_TRAILING_EDGE = sender.floatValue trailingInitEdgeLabel.title = "ITE: " + String( SPKR_INITIAL_TRAILING_EDGE ) } @IBOutlet weak var trailingEdgeLabel: NSTextFieldCell! @IBAction func trailingEdgeSelected(_ sender: NSSlider) { SPKR_FADE_TRAILING_EDGE = sender.floatValue trailingEdgeLabel.title = "TE: " + String( SPKR_FADE_TRAILING_EDGE ) } @IBOutlet weak var wozExtraLabel: NSTextFieldCell! @IBAction func wozExtraSelected(_ sender: NSSlider) { extraForward = Int32(sender.floatValue) wozExtraLabel.title = "WE: " + String( extraForward ) } @IBOutlet weak var EMALabel: NSTextFieldCell! @IBAction func EMASelected(_ sender: NSSlider) { spkr_ema_len = Int32(sender.floatValue) EMALabel.title = "EMA: " + String( spkr_ema_len ) } func setSimulationMode( mode : String ) { switch ( mode ) { case "Eco": cpuMode = cpuMode_eco fps = DEFAULT_FPS video_fps_divider = ECO_VIDEO_DIV spkr_fps_divider = DEF_SPKR_DIV break case "Game": cpuMode = cpuMode_game cpuState = cpuState_running fps = GAME_FPS video_fps_divider = GAME_VIDEO_DIV spkr_fps_divider = GAME_SPKR_DIV break default: cpuMode = cpuMode_normal cpuState = cpuState_running fps = DEFAULT_FPS video_fps_divider = DEF_VIDEO_DIV spkr_fps_divider = DEF_SPKR_DIV break } // spkr_fps_divider = fps / spkr_fps // spkr_fps = fps; spkr_play_timeout = SPKR_PLAY_TIMEOUT // * spkr_fps_divider // pixelTrail = pow(256, 1 / Double(fps / video_fps_divider / 3) ) // spkr_buf_size = spkr_sample_rate * 2 / spkr_fps #if SCHEDULER_CVDISPLAYLINK #else newUpdateTimer( timeInterval: 1 / Double(fps) ) #endif setCPUClockSpeed(freq: MHz_6502) // TODO: Better way to deal with speaker!!! spkr_play_timeout = SPKR_PLAY_TIMEOUT * Int32(video_fps_divider) } @IBAction func setCPUMode(_ sender: NSPopUpButton) { setSimulationMode(mode: sender.selectedItem?.title ?? "Normal" ) } @IBOutlet weak var initialLeadEdgeSlider: NSSlider! @IBOutlet weak var leadEdgeSlider: NSSlider! @IBOutlet weak var initialTailEdgeSlider: NSSlider! @IBOutlet weak var tailEdgeSlider: NSSlider! @IBOutlet weak var soundGapSlider: NSSlider! // @IBOutlet weak var soundGap: NSTextFieldCell! // // @IBAction func SoundGapChanged(_ sender: NSStepper) { // SoundGap.integerValue = sender.integerValue // spkr_extra_buf = Int32( sender.integerValue ) // } @IBAction func CRTMonitorOnOff(_ sender: NSButton) { CRTMonitor = sender.state == .on scanLines.isHidden = !CRTMonitor if ( CRTMonitor ) { textDisplay.textColor = .white // TODO: Adjust gamma so pixels are brighter } else { textDisplay.textColor = colorWhite // TODO: Adjust gamma so pixels are dimmer } hires.RenderFullScreen() } func ColorMonitorSelector( color : Bool ) { ColorMonitor = color if ( ColorMonitor ) { textDisplay.textColor = colorWhite // .white } else { textDisplay.textColor = colorGreen // .green } hires.RenderFullScreen() } @IBAction func ColorMonitorOnOff(_ sender: NSButton) { ColorMonitorSelector( color: sender.state == .on ) } func MonoMonitorChange( color: String ) { switch color { case "Green": ColorMonitor = false monoColor = colorGreen hires.monoColor = hires.color_green case "Amber": ColorMonitor = false monoColor = colorOrange hires.monoColor = hires.color_orange default: ColorMonitor = false monoColor = colorWhite hires.monoColor = hires.color_white } textDisplay.textColor = monoColor hires.RenderFullScreen() } @IBAction func MonitorChange(_ sender: NSButton) { switch sender.title { case "Green Mono": MonoMonitorChange(color: "Green") case "Amber Mono": MonoMonitorChange(color: "Amber") default: MonoMonitorChange(color: "White") } } @IBAction func Keyboard2JoystickOnOff(_ sender: NSButton) { Keyboard2Joystick = sender.state == .on } @IBAction func Mouse2JoystickOnOff(_ sender: NSButton) { Mouse2Joystick = sender.state == .on } @IBAction func MouseOnOff(_ sender: NSButton) { MouseInterface = sender.state == .on } @IBAction func QuickDisk(_ sender: NSButton) { if sender.state == .on { diskAccelerator_enabled = 1; } else { diskAccelerator_enabled = 0; } } @IBAction func DiskSound(_ sender: NSButton) { if sender.state == .on { disk_sfx_enabled = 1; } else { disk_sfx_enabled = 0; } } func openDiskImage( url: URL ) { woz_eject() switch url.pathExtension.uppercased() { case "WOZ": let err = woz_loadFile( url.path ) if err == WOZ_ERR_OK { NSDocumentController.shared.noteNewRecentDocumentURL(URL(fileURLWithPath: url.path)) } else { self.chk_woz_load(err: err) } case "DSK", "DO", "PO" : let err = dsk2woz( url.path ) if err == WOZ_ERR_OK { let err = woz_parseBuffer() if err == WOZ_ERR_OK { NSDocumentController.shared.noteNewRecentDocumentURL(URL(fileURLWithPath: url.path)) } } else { self.chk_woz_load(err: err) } default: break } } @objc func openDiskImageDialog() { let openPanel = NSOpenPanel() openPanel.title = "Open Disk Image" openPanel.allowsMultipleSelection = false openPanel.canChooseDirectories = false openPanel.canCreateDirectories = false openPanel.canChooseFiles = true openPanel.allowedFileTypes = ["dsk","do","po","nib", "woz"] openPanel.begin { (result) -> Void in if result == NSApplication.ModalResponse.OK { // print("file:", openPanel.url!.path) //Do what you will //If there's only one URL, surely 'openPanel.URL' //but otherwise a for loop works if let url = openPanel.url { self.openDiskImage(url: url) } else { let a = NSAlert() a.messageText = "File Not Found" a.informativeText = "Could not locate selected file" a.alertStyle = .critical a.beginSheetModal( for: self.view.window! ) } } } } @objc func saveDiskImage() { let openPanel = NSOpenPanel() openPanel.title = "Save Disk Image" openPanel.allowsMultipleSelection = false openPanel.canChooseDirectories = false openPanel.canCreateDirectories = false openPanel.canChooseFiles = true openPanel.allowedFileTypes = ["dsk","do","po","nib", "woz"] openPanel.begin { (result) -> Void in if result == NSApplication.ModalResponse.OK { // print("file:", openPanel.url!.path) //Do what you will //If there's only one URL, surely 'openPanel.URL' //but otherwise a for loop works if let filePath = openPanel.url?.path { let woz_err = woz_saveFile( filePath ) if woz_err == WOZ_ERR_OK { NSDocumentController.shared.noteNewRecentDocumentURL(URL(fileURLWithPath: filePath)) } else { self.chk_woz_load(err: woz_err) } } } } } @IBOutlet weak var QuickDisk_Disk1: NSMenuItem! @IBOutlet weak var QuickDisk_Disk2: NSMenuItem! @IBOutlet weak var DiskSound_Disk1: NSMenuItem! @IBOutlet weak var DiskSound_Disk2: NSMenuItem! @IBAction func Disk1(_ sender: NSPopUpButton) { switch sender.selectedItem?.tag { case 1: // Open openDiskImageDialog() case 2: // Save saveFile() case 3: // Save As... saveFileAs() case 4: // Eject woz_eject() case 21: // Quick Disk if diskAccelerator_enabled == 0 { diskAccelerator_enabled = 1; } else { diskAccelerator_enabled = 0; } let state : NSControl.StateValue = diskAccelerator_enabled == 1 ? .on : .off QuickDisk_Disk1?.state = state QuickDisk_Disk2?.state = state break case 22: // Disk Sound if disk_sfx_enabled == 0 { disk_sfx_enabled = 1; } else { disk_sfx_enabled = 0; } let state : NSControl.StateValue = disk_sfx_enabled == 1 ? .on : .off DiskSound_Disk1?.state = state DiskSound_Disk2?.state = state break case 1000: // Open Default Disk Image if let menuIdentifier = sender.selectedItem?.title { let woz_err = woz_loadFile( Bundle.main.resourcePath! + "/dsk/" + menuIdentifier + ".woz" ) ViewController.shared?.chk_woz_load(err: woz_err) woz_flags.image_file_readonly = 1 } default: break } } @IBAction func traceEnable(_ sender: NSButton) { switch sender.state { case .on: m6502.debugger.mask.trace = 1 openLog() default: m6502.debugger.mask.trace = 0 closeLog() } } func saveFile() { if ( woz_flags.image_file_readonly != 0 ) { // it is readonly, save it to a different file... saveFileAs() } else { // save WOZ image file overwriting the original image woz_saveFile(nil) } } func saveFileAs() { let savePanel = NSSavePanel() savePanel.title = "Save WOZ Disk Image As..." savePanel.begin { (result) in if result.rawValue == NSApplication.ModalResponse.OK.rawValue { woz_saveFile( savePanel.url?.path ); } else { let a = NSAlert() a.messageText = "Are you sure?" a.informativeText = "Are you sure you would like to cancel and lose all modification you have made to the Disk Image?\nALERT: You will lose all new files and modified files from this Disk Image since you loaded. Your decision if permanent and irreversible!" a.addButton(withTitle: "Save") a.addButton(withTitle: "Cancel") a.alertStyle = .warning a.beginSheetModal(for: self.view.window!, completionHandler: { (modalResponse) -> Void in if modalResponse == .alertFirstButtonReturn { self.saveFileAs() } }) } } } func Cheat_Wavy_Navy_Victory() { #if !DEBUGGER JUMP( 0x1528 ) // called when player clears the level #endif } func Cheat_Wavy_Navy_Add_3_Ships() { #if !DEBUGGER let ships = min( getMEM( 0x4746 ) + 3, 9 ) setMEM( 0x4746, ships ) m6502.A = ships // m6502.X = 0x10 setMEM16(0x4728, 0x16F0) // cursor pos: 0x4728:x, 0x4729:y CALL( 0x1FDA ) // position and number needed? A:Number of ships X:0x10 #endif } func Cheat_Wavy_Navy_OtherCheats() { #if !DEBUGGER // Replace STC / SBC $0x1 to NOPs... setMEM( 0x1E63, 0xEA ) setMEM( 0x1E64, 0xEA ) setMEM( 0x1E65, 0xEA ) // // call to decease // setMEM( 0x1556, 0xEA ); // setMEM( 0x1557, 0xEA ); // setMEM( 0x1558, 0xEA ); // setMEM( 0x15F3, 0xEA ); // setMEM( 0x15F4, 0xEA ); // setMEM( 0x15F5, 0xEA ); // setMEM( 0x15EA, 0xEA ); // setMEM( 0x15EB, 0xEA ); // // no end // setMEM( 0x1515, 0xEA ); // setMEM( 0x1516, 0xEA ); // // no end // setMEM( 0x1537, 0xEA ); // setMEM( 0x1538, 0xEA ); // lose to win setMEM( 0x1545, 0xEA ) setMEM( 0x1546, 0xEA ) // var i : UInt16 = 0x15EA; // while i < 0x1608 { // setMEM( i, 0xEA ) // i += 1 // } #endif } func Cheat_Wavy_Navy_Never_Lose() -> NSControl.StateValue { #if !DEBUGGER // Replace STC / SBC #$01 to NOPs... // setMEM( 0x1E63, 0xEA ) // setMEM( 0x1E64, 0xEA ) // setMEM( 0x1E65, 0xEA ) if ( getMEM16(0x1E64) == 0x01E9 ) { // SBC #$01 // Replace SBC #$01 to SBC #$00... setMEM( 0x1E65, 0 ) return .on } else if ( getMEM16(0x1E64) == 0x00E9 ) { // SBC #$00 // Replace SBC #$00 to SBC #$01... setMEM( 0x1E65, 1 ) return .off } else { print("Not Wavy Navy!") return .off } #else return .off #endif } func Cheat_Wavy_Navy_Lose_To_Win() -> NSControl.StateValue { #if !DEBUGGER if ( getMEM16(0x1545) == 0x09F0 ) { // BEQ $1550 // lose to win setMEM16( 0x1545, 0xEAEA ) // NOP NOP return .on } else if ( getMEM16(0x1545) == 0xEAEA ) { // NOP NOP // lose to win setMEM16( 0x1545, 0x09F0 ) // BEQ $1550 return .off } else { print("Not Wavy Navy!") return .off } #else return .off #endif } func Get_Hard_Hat_Mack() -> UInt8 { #if !DEBUGGER return getMEM( 0x4EDF ) #else return 0 #endif } func Cheat_Hard_Hat_Mack(add : UInt8) -> UInt8 { #if !DEBUGGER let ships = min( getMEM( 0x4EDF ) + add, 9 ) setMEM( 0x4EDF, ships ) // CALL( 0x1219 ) // starts from the beginning CALL( 0x1A2B ) // refresh Mack counter on screen return ships #else return 0 #endif } func Cheat_Hard_Hat_Mack_Never_Lose() -> NSControl.StateValue { #if !DEBUGGER setMEM( 0x0503, 0x18 ) setMEM( 0x0504, 0x60 ) setMEM( 0x50A5, 0xEA ) setMEM( 0x50A6, 0xEA ) setMEM( 0x50A7, 0xEA ) #endif return .on } } @_cdecl("woz_ask_to_save") func woz_ask_to_save() { ViewController.shared?.saveFile() }