diff --git a/OctoPod Mac/AppDelegate.swift b/OctoPod Mac/AppDelegate.swift
new file mode 100644
index 00000000..e3f8269f
--- /dev/null
+++ b/OctoPod Mac/AppDelegate.swift
@@ -0,0 +1,149 @@
+//
+// AppDelegate.swift
+// OctoPod Mac
+//
+// Created by Arijit Banerjee on 6/26/20.
+// Copyright © 2020 Gaston Dombiak. All rights reserved.
+//
+
+import Cocoa
+import UserNotifications
+
+@NSApplicationMain
+class AppDelegate: NSObject, NSApplicationDelegate {
+
+ lazy var preferencesWindowController = { () -> NSWindowController in
+ let storyBoard = NSStoryboard(name: "Main", bundle: nil)
+ guard let pwc = storyBoard.instantiateController(withIdentifier: "PreferencesWindowController") as? NSWindowController else{
+ fatalError("Unable to instantiate view controller")
+ }
+ (pwc.contentViewController as! PreferencesViewController).delegate = popoverViewController
+ return pwc
+ }()
+
+ let statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.squareLength)
+
+ lazy var popoverViewController = { () -> PopoverViewController in
+ let storyBoard = NSStoryboard(name: "Main", bundle: nil)
+ guard let vc = storyBoard.instantiateController(withIdentifier: "PopoverViewController") as? PopoverViewController
+ else{
+ fatalError("Unable to instantiate view controller")
+ }
+ return vc
+ }()
+
+ func applicationDidFinishLaunching(_ aNotification: Notification) {
+ if #available(OSX 10.14, *) {
+ registerForPushNotifications()
+ } else {
+ // Fallback on earlier versions
+ }
+ let itemImage = NSImage(named: NSImage.Name("Status-Icon"))
+ itemImage?.size = NSMakeSize(20.0, 20.0);
+ itemImage?.isTemplate = true
+ statusItem.button?.image = itemImage
+ statusItem.button?.sendAction(on: [.leftMouseUp, .rightMouseUp])
+ statusItem.button?.target = self
+ statusItem.button?.action = #selector(statusBarButtonClicked)
+ statusItem.button?.toolTip = "OctoPod for OctoPrint"
+ }
+
+ func applicationWillTerminate(_ aNotification: Notification) {
+ // Insert code here to tear down your application
+ }
+ lazy var appConfiguration: AppConfiguration = {
+ return AppConfiguration()
+ }()
+ func showQuickView() {
+ guard let button = statusItem.button else {
+ fatalError("Cannot get status button")
+ }
+ let popoverView = NSPopover()
+ popoverView.contentViewController = popoverViewController
+ popoverView.behavior = .transient
+ popoverView.show(relativeTo: button.bounds, of: button, preferredEdge: .maxY)
+ }
+
+ @objc func showPreferences() {
+ preferencesWindowController.showWindow(self)
+ }
+
+ lazy var menu = getMenu()
+ private func getMenu() -> NSMenu {
+ let menu = NSMenu()
+ menu.addItem(NSMenuItem(title: "About OctoPod Mac", action: #selector(NSApplication.orderFrontStandardAboutPanel(_:)), keyEquivalent: ""))
+ menu.addItem(NSMenuItem(title: "Preferences", action: #selector(showPreferences), keyEquivalent: "P"))
+ menu.addItem(NSMenuItem.separator())
+ menu.addItem(NSMenuItem(title: "Quit OctoPod Mac", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q"))
+ return menu
+ }
+
+ @objc func statusBarButtonClicked(sender: NSStatusBarButton) {
+ let event = NSApp.currentEvent!
+ if event.type == NSEvent.EventType.rightMouseUp {
+ NSMenu.popUpContextMenu(menu, with: event, for: sender)
+ } else {
+ showQuickView()
+ }
+ }
+ @available(OSX 10.14, *)
+ func registerForPushNotifications() {
+ UNUserNotificationCenter.current() // 1
+ .requestAuthorization(options: [.alert, .sound, .badge]) { // 2
+ granted, error in
+ guard granted else { return }
+ self.getNotificationSettings()
+ }
+ }
+
+ @available(OSX 10.14, *)
+ func getNotificationSettings() {
+ UNUserNotificationCenter.current().getNotificationSettings { settings in
+ NSLog("Notification settings: \(settings)")
+ }
+ }
+
+ lazy var printerManager: PrinterManager? = {
+ let context = persistentContainer.viewContext
+ context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
+ var printerManager = PrinterManager()
+ printerManager.managedObjectContext = context
+ return printerManager
+ }()
+
+ // MARK: - Core Data stack
+
+ lazy var persistentContainer: SharedPersistentContainer = {
+ /*
+ The persistent container for the application. This implementation
+ creates and returns a container, having loaded the store for the
+ application to it. This property is optional since there are legitimate
+ error conditions that could cause the creation of the store to fail.
+ */
+ let container = SharedPersistentContainer(name: "OctoPod")
+ container.loadPersistentStores(completionHandler: { (storeDescription, error) in
+ if let error = error as NSError? {
+ // Replace this implementation with code to handle the error appropriately.
+ // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
+
+ /*
+ Typical reasons for an error here include:
+ * The parent directory does not exist, cannot be created, or disallows writing.
+ * The persistent store is not accessible, due to permissions or data protection when the device is locked.
+ * The device is out of space.
+ * The store could not be migrated to the current model version.
+ Check the error message to determine what the actual problem was.
+ */
+ fatalError("Unresolved error \(error), \(error.userInfo)")
+ }
+ })
+ return container
+ }()
+
+ @IBAction func preferencesFromMainMenuClicked(_ sender: Any) {
+ showPreferences()
+ }
+
+}
+
+
diff --git a/OctoPod Mac/Assets.xcassets/AppIcon-Mac.appiconset/1024.png b/OctoPod Mac/Assets.xcassets/AppIcon-Mac.appiconset/1024.png
new file mode 100644
index 00000000..e4a391d4
Binary files /dev/null and b/OctoPod Mac/Assets.xcassets/AppIcon-Mac.appiconset/1024.png differ
diff --git a/OctoPod Mac/Assets.xcassets/AppIcon-Mac.appiconset/128.png b/OctoPod Mac/Assets.xcassets/AppIcon-Mac.appiconset/128.png
new file mode 100644
index 00000000..1988944a
Binary files /dev/null and b/OctoPod Mac/Assets.xcassets/AppIcon-Mac.appiconset/128.png differ
diff --git a/OctoPod Mac/Assets.xcassets/AppIcon-Mac.appiconset/16.png b/OctoPod Mac/Assets.xcassets/AppIcon-Mac.appiconset/16.png
new file mode 100644
index 00000000..92af0385
Binary files /dev/null and b/OctoPod Mac/Assets.xcassets/AppIcon-Mac.appiconset/16.png differ
diff --git a/OctoPod Mac/Assets.xcassets/AppIcon-Mac.appiconset/256-1.png b/OctoPod Mac/Assets.xcassets/AppIcon-Mac.appiconset/256-1.png
new file mode 100644
index 00000000..fe2e5051
Binary files /dev/null and b/OctoPod Mac/Assets.xcassets/AppIcon-Mac.appiconset/256-1.png differ
diff --git a/OctoPod Mac/Assets.xcassets/AppIcon-Mac.appiconset/256.png b/OctoPod Mac/Assets.xcassets/AppIcon-Mac.appiconset/256.png
new file mode 100644
index 00000000..fe2e5051
Binary files /dev/null and b/OctoPod Mac/Assets.xcassets/AppIcon-Mac.appiconset/256.png differ
diff --git a/OctoPod Mac/Assets.xcassets/AppIcon-Mac.appiconset/32-1.png b/OctoPod Mac/Assets.xcassets/AppIcon-Mac.appiconset/32-1.png
new file mode 100644
index 00000000..fc5ab923
Binary files /dev/null and b/OctoPod Mac/Assets.xcassets/AppIcon-Mac.appiconset/32-1.png differ
diff --git a/OctoPod Mac/Assets.xcassets/AppIcon-Mac.appiconset/32.png b/OctoPod Mac/Assets.xcassets/AppIcon-Mac.appiconset/32.png
new file mode 100644
index 00000000..fc5ab923
Binary files /dev/null and b/OctoPod Mac/Assets.xcassets/AppIcon-Mac.appiconset/32.png differ
diff --git a/OctoPod Mac/Assets.xcassets/AppIcon-Mac.appiconset/512-1.png b/OctoPod Mac/Assets.xcassets/AppIcon-Mac.appiconset/512-1.png
new file mode 100644
index 00000000..1b92ede9
Binary files /dev/null and b/OctoPod Mac/Assets.xcassets/AppIcon-Mac.appiconset/512-1.png differ
diff --git a/OctoPod Mac/Assets.xcassets/AppIcon-Mac.appiconset/512.png b/OctoPod Mac/Assets.xcassets/AppIcon-Mac.appiconset/512.png
new file mode 100644
index 00000000..1b92ede9
Binary files /dev/null and b/OctoPod Mac/Assets.xcassets/AppIcon-Mac.appiconset/512.png differ
diff --git a/OctoPod Mac/Assets.xcassets/AppIcon-Mac.appiconset/64.png b/OctoPod Mac/Assets.xcassets/AppIcon-Mac.appiconset/64.png
new file mode 100644
index 00000000..58664699
Binary files /dev/null and b/OctoPod Mac/Assets.xcassets/AppIcon-Mac.appiconset/64.png differ
diff --git a/OctoPod Mac/Assets.xcassets/AppIcon-Mac.appiconset/Contents.json b/OctoPod Mac/Assets.xcassets/AppIcon-Mac.appiconset/Contents.json
new file mode 100644
index 00000000..d12465d0
--- /dev/null
+++ b/OctoPod Mac/Assets.xcassets/AppIcon-Mac.appiconset/Contents.json
@@ -0,0 +1,68 @@
+{
+ "images" : [
+ {
+ "filename" : "16.png",
+ "idiom" : "mac",
+ "scale" : "1x",
+ "size" : "16x16"
+ },
+ {
+ "filename" : "32.png",
+ "idiom" : "mac",
+ "scale" : "2x",
+ "size" : "16x16"
+ },
+ {
+ "filename" : "32-1.png",
+ "idiom" : "mac",
+ "scale" : "1x",
+ "size" : "32x32"
+ },
+ {
+ "filename" : "64.png",
+ "idiom" : "mac",
+ "scale" : "2x",
+ "size" : "32x32"
+ },
+ {
+ "filename" : "128.png",
+ "idiom" : "mac",
+ "scale" : "1x",
+ "size" : "128x128"
+ },
+ {
+ "filename" : "256.png",
+ "idiom" : "mac",
+ "scale" : "2x",
+ "size" : "128x128"
+ },
+ {
+ "filename" : "256-1.png",
+ "idiom" : "mac",
+ "scale" : "1x",
+ "size" : "256x256"
+ },
+ {
+ "filename" : "512.png",
+ "idiom" : "mac",
+ "scale" : "2x",
+ "size" : "256x256"
+ },
+ {
+ "filename" : "512-1.png",
+ "idiom" : "mac",
+ "scale" : "1x",
+ "size" : "512x512"
+ },
+ {
+ "filename" : "1024.png",
+ "idiom" : "mac",
+ "scale" : "2x",
+ "size" : "512x512"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/OctoPod Mac/Assets.xcassets/Contents.json b/OctoPod Mac/Assets.xcassets/Contents.json
new file mode 100644
index 00000000..73c00596
--- /dev/null
+++ b/OctoPod Mac/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/OctoPod Mac/Assets.xcassets/Octopod.imageset/128.png b/OctoPod Mac/Assets.xcassets/Octopod.imageset/128.png
new file mode 100644
index 00000000..1988944a
Binary files /dev/null and b/OctoPod Mac/Assets.xcassets/Octopod.imageset/128.png differ
diff --git a/OctoPod Mac/Assets.xcassets/Octopod.imageset/256.png b/OctoPod Mac/Assets.xcassets/Octopod.imageset/256.png
new file mode 100644
index 00000000..fe2e5051
Binary files /dev/null and b/OctoPod Mac/Assets.xcassets/Octopod.imageset/256.png differ
diff --git a/OctoPod Mac/Assets.xcassets/Octopod.imageset/512.png b/OctoPod Mac/Assets.xcassets/Octopod.imageset/512.png
new file mode 100644
index 00000000..1b92ede9
Binary files /dev/null and b/OctoPod Mac/Assets.xcassets/Octopod.imageset/512.png differ
diff --git a/OctoPod Mac/Assets.xcassets/Octopod.imageset/Contents.json b/OctoPod Mac/Assets.xcassets/Octopod.imageset/Contents.json
new file mode 100644
index 00000000..868bfdac
--- /dev/null
+++ b/OctoPod Mac/Assets.xcassets/Octopod.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "filename" : "128.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "256.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "filename" : "512.png",
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/OctoPod Mac/Assets.xcassets/Print Job/Cancel.imageset/Contents.json b/OctoPod Mac/Assets.xcassets/Print Job/Cancel.imageset/Contents.json
new file mode 100644
index 00000000..87bf5371
--- /dev/null
+++ b/OctoPod Mac/Assets.xcassets/Print Job/Cancel.imageset/Contents.json
@@ -0,0 +1,22 @@
+{
+ "images" : [
+ {
+ "filename" : "cancel_32.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "cancel_64.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/OctoPod Mac/Assets.xcassets/Print Job/Cancel.imageset/cancel_32.png b/OctoPod Mac/Assets.xcassets/Print Job/Cancel.imageset/cancel_32.png
new file mode 100644
index 00000000..8895937a
Binary files /dev/null and b/OctoPod Mac/Assets.xcassets/Print Job/Cancel.imageset/cancel_32.png differ
diff --git a/OctoPod Mac/Assets.xcassets/Print Job/Cancel.imageset/cancel_64.png b/OctoPod Mac/Assets.xcassets/Print Job/Cancel.imageset/cancel_64.png
new file mode 100644
index 00000000..18664dd8
Binary files /dev/null and b/OctoPod Mac/Assets.xcassets/Print Job/Cancel.imageset/cancel_64.png differ
diff --git a/OctoPod Mac/Assets.xcassets/Print Job/Contents.json b/OctoPod Mac/Assets.xcassets/Print Job/Contents.json
new file mode 100644
index 00000000..73c00596
--- /dev/null
+++ b/OctoPod Mac/Assets.xcassets/Print Job/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/OctoPod Mac/Assets.xcassets/Print Job/Pause.imageset/Contents.json b/OctoPod Mac/Assets.xcassets/Print Job/Pause.imageset/Contents.json
new file mode 100644
index 00000000..27bffe85
--- /dev/null
+++ b/OctoPod Mac/Assets.xcassets/Print Job/Pause.imageset/Contents.json
@@ -0,0 +1,22 @@
+{
+ "images" : [
+ {
+ "filename" : "pause_32.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "pause_64.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/OctoPod Mac/Assets.xcassets/Print Job/Pause.imageset/pause_32.png b/OctoPod Mac/Assets.xcassets/Print Job/Pause.imageset/pause_32.png
new file mode 100644
index 00000000..eaf01f45
Binary files /dev/null and b/OctoPod Mac/Assets.xcassets/Print Job/Pause.imageset/pause_32.png differ
diff --git a/OctoPod Mac/Assets.xcassets/Print Job/Pause.imageset/pause_64.png b/OctoPod Mac/Assets.xcassets/Print Job/Pause.imageset/pause_64.png
new file mode 100644
index 00000000..0e43da11
Binary files /dev/null and b/OctoPod Mac/Assets.xcassets/Print Job/Pause.imageset/pause_64.png differ
diff --git a/OctoPod Mac/Assets.xcassets/Print Job/Print.imageset/Contents.json b/OctoPod Mac/Assets.xcassets/Print Job/Print.imageset/Contents.json
new file mode 100644
index 00000000..04fde351
--- /dev/null
+++ b/OctoPod Mac/Assets.xcassets/Print Job/Print.imageset/Contents.json
@@ -0,0 +1,22 @@
+{
+ "images" : [
+ {
+ "filename" : "play_32.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "play_64.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/OctoPod Mac/Assets.xcassets/Print Job/Print.imageset/play_32.png b/OctoPod Mac/Assets.xcassets/Print Job/Print.imageset/play_32.png
new file mode 100644
index 00000000..bbdc08ef
Binary files /dev/null and b/OctoPod Mac/Assets.xcassets/Print Job/Print.imageset/play_32.png differ
diff --git a/OctoPod Mac/Assets.xcassets/Print Job/Print.imageset/play_64.png b/OctoPod Mac/Assets.xcassets/Print Job/Print.imageset/play_64.png
new file mode 100644
index 00000000..c3acb64c
Binary files /dev/null and b/OctoPod Mac/Assets.xcassets/Print Job/Print.imageset/play_64.png differ
diff --git a/OctoPod Mac/Assets.xcassets/Print Job/Restart.imageset/Contents.json b/OctoPod Mac/Assets.xcassets/Print Job/Restart.imageset/Contents.json
new file mode 100644
index 00000000..4da36af4
--- /dev/null
+++ b/OctoPod Mac/Assets.xcassets/Print Job/Restart.imageset/Contents.json
@@ -0,0 +1,22 @@
+{
+ "images" : [
+ {
+ "filename" : "restart_32.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "restart_64.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/OctoPod Mac/Assets.xcassets/Print Job/Restart.imageset/restart_32.png b/OctoPod Mac/Assets.xcassets/Print Job/Restart.imageset/restart_32.png
new file mode 100644
index 00000000..e98f6ddd
Binary files /dev/null and b/OctoPod Mac/Assets.xcassets/Print Job/Restart.imageset/restart_32.png differ
diff --git a/OctoPod Mac/Assets.xcassets/Print Job/Restart.imageset/restart_64.png b/OctoPod Mac/Assets.xcassets/Print Job/Restart.imageset/restart_64.png
new file mode 100644
index 00000000..8b0df4bc
Binary files /dev/null and b/OctoPod Mac/Assets.xcassets/Print Job/Restart.imageset/restart_64.png differ
diff --git a/OctoPod Mac/Assets.xcassets/Status-Icon.imageset/Contents.json b/OctoPod Mac/Assets.xcassets/Status-Icon.imageset/Contents.json
new file mode 100644
index 00000000..bdd46e8d
--- /dev/null
+++ b/OctoPod Mac/Assets.xcassets/Status-Icon.imageset/Contents.json
@@ -0,0 +1,22 @@
+{
+ "images" : [
+ {
+ "filename" : "Octopod-48x48.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "Octopod-48x48-1.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/OctoPod Mac/Assets.xcassets/Status-Icon.imageset/Octopod-48x48-1.png b/OctoPod Mac/Assets.xcassets/Status-Icon.imageset/Octopod-48x48-1.png
new file mode 100644
index 00000000..26e2b090
Binary files /dev/null and b/OctoPod Mac/Assets.xcassets/Status-Icon.imageset/Octopod-48x48-1.png differ
diff --git a/OctoPod Mac/Assets.xcassets/Status-Icon.imageset/Octopod-48x48.png b/OctoPod Mac/Assets.xcassets/Status-Icon.imageset/Octopod-48x48.png
new file mode 100644
index 00000000..26e2b090
Binary files /dev/null and b/OctoPod Mac/Assets.xcassets/Status-Icon.imageset/Octopod-48x48.png differ
diff --git a/OctoPod Mac/CameraImageView.swift b/OctoPod Mac/CameraImageView.swift
new file mode 100644
index 00000000..9aa7e7ed
--- /dev/null
+++ b/OctoPod Mac/CameraImageView.swift
@@ -0,0 +1,89 @@
+//
+// CameraImageView.swift
+// OctoPod Mac
+//
+// Created by Arijit Banerjee on 6/29/20.
+// Copyright © 2020 Gaston Dombiak. All rights reserved.
+//
+
+import Foundation
+import Cocoa
+import Carbon.HIToolbox
+class CameraImageView: NSImageView {
+ private var currentOrientation = 0
+ private lazy var printerStatusOverlay = overlayableLabel()
+ private var extruderTempDisplay = 0.0
+ private var bedTempDisplay = 0.0
+ private var progress = "-"
+ private var timeLeft = "-"
+ private var progressCompletionDisplay = 0.0
+ override func mouseUp(with event: NSEvent) {
+ if event.clickCount == 2{
+ toggleFullScreen()
+ }
+ }
+ override func rotate(byDegrees angle: CGFloat) {
+ DispatchQueue.main.async {
+ //restore from existing rotation
+ super.rotate(byDegrees: CGFloat(-self.currentOrientation))
+ //apply new rotation
+ super.rotate(byDegrees: CGFloat(angle))
+ }
+ currentOrientation = Int(angle)
+ }
+ override func keyDown(with event: NSEvent) {
+ if event.keyCode == kVK_Escape{
+ toggleFullScreen()
+ }
+ }
+ private func toggleFullScreen(){
+ if !self.isInFullScreenMode{
+ self.enterFullScreenMode(NSScreen.main!, withOptions: nil)
+ //overlayPrinterDetails(visible: true)
+
+ }else{
+ self.exitFullScreenMode(options: nil)
+ (NSApp.delegate as! AppDelegate).showQuickView()
+ //overlayPrinterDetails(visible: false)
+ }
+ }
+ func setPrinerDetails(printerStatus:String?,actualExtruderTemp:Double?,targetExtruderTemp:Double?,actualBedTemp:Double?,targetBedTemp:Double?,progressPrintTime:Int?,progressPrintTimeLeft:Int?, progressCompletion:Double?){
+ extruderTempDisplay = actualExtruderTemp ?? extruderTempDisplay
+ bedTempDisplay = actualBedTemp ?? bedTempDisplay
+ let progressPrintTimeLeftDouble = Double(progressPrintTimeLeft ?? 0)
+ timeLeft = UIUtils.secondsToEstimatedPrintTime(seconds: progressPrintTimeLeftDouble)
+ progressCompletionDisplay = progressCompletion?.round(to: 1) ?? progressCompletionDisplay
+ printerStatusOverlay.stringValue = "Extruder:\(extruderTempDisplay) Bed:\(bedTempDisplay) Complete: \(progressCompletionDisplay)%"
+ }
+ func overlayPrinterDetails(visible:Bool) {
+ if(visible){
+ let x = 20
+ let y = 20
+ printerStatusOverlay.setFrameOrigin(CGPoint(x: x, y: y))
+
+
+ self.addSubview(printerStatusOverlay)
+ }
+ else{
+ printerStatusOverlay.removeFromSuperview()
+ }
+ }
+
+ private func overlayableLabel() -> NSTextField {
+ let screenWidth = NSScreen.main?.frame.width ?? 100
+ let label = NSTextField(frame: NSMakeRect(0,0,screenWidth - 200,25))
+ label.isEditable = false
+ label.isSelectable = false
+ label.textColor = .labelColor
+ let backgroundColor = NSColor.controlBackgroundColor
+ label.backgroundColor = backgroundColor.withAlphaComponent(CGFloat(0.5))
+ label.drawsBackground = true
+ label.isBezeled = false
+ label.alignment = .natural
+ label.font = NSFont.systemFont(ofSize: NSFont.systemFontSize(for: label.controlSize))
+ label.lineBreakMode = .byClipping
+ label.cell?.isScrollable = false
+ label.cell?.wraps = false
+ return label
+ }
+}
diff --git a/OctoPod Mac/Extensions.swift b/OctoPod Mac/Extensions.swift
new file mode 100644
index 00000000..97e5d115
--- /dev/null
+++ b/OctoPod Mac/Extensions.swift
@@ -0,0 +1,16 @@
+//
+// Extensions.swift
+// OctoPod Mac
+//
+// Created by Arijit Banerjee on 6/30/20.
+// Copyright © 2020 Gaston Dombiak. All rights reserved.
+//
+
+import Foundation
+
+extension Double {
+ func round(to places: Int) -> Double {
+ let divisor = pow(10.0, Double(places))
+ return (self * divisor).rounded() / divisor
+ }
+}
diff --git a/OctoPod Mac/Info.plist b/OctoPod Mac/Info.plist
new file mode 100644
index 00000000..1fc17e1d
--- /dev/null
+++ b/OctoPod Mac/Info.plist
@@ -0,0 +1,45 @@
+
+
+
+
+ NSAppTransportSecurity
+
+ NSAllowsArbitraryLoads
+
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIconFile
+
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ $(PRODUCT_BUNDLE_PACKAGE_TYPE)
+ CFBundleShortVersionString
+ 1.0
+ CFBundleVersion
+ 1
+ LSApplicationCategoryType
+ public.app-category.utilities
+ LSMinimumSystemVersion
+ $(MACOSX_DEPLOYMENT_TARGET)
+ LSUIElement
+
+ NSHumanReadableCopyright
+ Copyright © 2020 Gaston Dombiak. All rights reserved.
+ NSMainStoryboardFile
+ Main
+ NSPrincipalClass
+ NSApplication
+ NSSupportsAutomaticTermination
+
+ NSSupportsSuddenTermination
+
+
+
diff --git a/OctoPod Mac/MJPEG Streaming/MjpegStreamingController.swift b/OctoPod Mac/MJPEG Streaming/MjpegStreamingController.swift
new file mode 100644
index 00000000..66d398d6
--- /dev/null
+++ b/OctoPod Mac/MJPEG Streaming/MjpegStreamingController.swift
@@ -0,0 +1,174 @@
+//
+// MjpegStreamingController.swift
+// MjpegStreamingKit
+//
+// Created by Stefano Vettor on 28/03/16.
+// Copyright © 2016 Stefano Vettor. All rights reserved.
+//
+// Modified for better error handling
+// Modified to allow image rotation
+//
+
+import Cocoa
+
+open class MjpegStreamingController: NSObject, URLSessionDataDelegate {
+
+ fileprivate enum Status {
+ case stopped
+ case loading
+ case playing
+ }
+
+ fileprivate var receivedData: NSMutableData?
+ fileprivate var dataTask: URLSessionDataTask?
+ fileprivate var session: Foundation.URLSession!
+ fileprivate var status: Status = .stopped
+
+ open var authenticationHandler: ((URLAuthenticationChallenge) -> (Foundation.URLSession.AuthChallengeDisposition, URLCredential?))?
+ open var authenticationFailedHandler: (()->Void)?
+ open var didStartLoading: (()->Void)?
+ open var didFinishLoading: (()->Void)?
+ open var didFinishWithErrors: ((Error)->Void)?
+ open var didFinishWithHTTPErrors: ((HTTPURLResponse)->Void)?
+ open var didFetchImage: ((NSImage)->Void)?
+ open var didRenderImage: ((NSImage)->Void)?
+ open var contentURL: URL?
+ open var imageView: NSImageView?
+
+ public override init() {
+ super.init()
+ self.session = Foundation.URLSession(configuration: URLSessionConfiguration.default, delegate: self, delegateQueue: nil)
+ }
+
+ public convenience init(imageView: NSImageView) {
+ self.init()
+ self.imageView = imageView
+ }
+
+ public convenience init(imageView: NSImageView, contentURL: URL) {
+ self.init(imageView: imageView)
+ self.contentURL = contentURL
+ }
+
+ deinit {
+ dataTask?.cancel()
+ }
+
+ open func play(url: URL){
+ if status == .playing || status == .loading {
+ stop()
+ }
+ contentURL = url
+ play()
+ }
+
+ open func play() {
+ guard let url = contentURL , status == .stopped else {
+ return
+ }
+
+ status = .loading
+ executeBlock { self.didStartLoading?() }
+
+ receivedData = NSMutableData()
+ let request = URLRequest(url: url)
+ dataTask = session.dataTask(with: request)
+ dataTask?.resume()
+ }
+
+ open func stop(){
+ status = .stopped
+ dataTask?.cancel()
+ }
+
+ // MARK: - NSURLSessionDataDelegate
+
+ open func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {
+ if let httpResponse = response as? HTTPURLResponse, let httpErrorHandler = didFinishWithHTTPErrors {
+ if httpResponse.statusCode == 404 || httpResponse.statusCode > 500 {
+ httpErrorHandler(httpResponse)
+ return
+ }
+ }
+ if let imageData = receivedData , imageData.length > 0,
+ let receivedImage = NSImage(data: imageData as Data) {
+
+ // I'm creating the NSImage before performing didFinishLoading to minimize the interval
+ // between the actions done by didFinishLoading and the appearance of the first image
+ var firstTimeImage = false
+ if status == .loading {
+ firstTimeImage = true
+ status = .playing
+ executeBlock { self.didFinishLoading?() }
+ }
+
+ executeBlock {
+ self.imageView?.image = receivedImage
+ self.didFetchImage?(receivedImage)
+ }
+
+ if firstTimeImage {
+ executeBlock { self.didRenderImage?(receivedImage) }
+ }
+ }
+
+ receivedData = NSMutableData()
+ completionHandler(.allow)
+ }
+
+ open func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
+ receivedData?.append(data)
+ }
+
+ // MARK: - NSURLSessionTaskDelegate
+
+ open func urlSession(_ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
+
+ var credential: URLCredential?
+ var disposition: Foundation.URLSession.AuthChallengeDisposition = .performDefaultHandling
+
+ if challenge.previousFailureCount > 0 {
+ // User credentials are incorrect
+ if let onAuthenticationFailed = authenticationFailedHandler {
+ onAuthenticationFailed()
+ }
+ // Cancel authentication flow
+ completionHandler(Foundation.URLSession.AuthChallengeDisposition.cancelAuthenticationChallenge, nil)
+ } else {
+ if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
+ if let trust = challenge.protectionSpace.serverTrust {
+ credential = URLCredential(trust: trust)
+ disposition = .useCredential
+ }
+ } else if let onAuthentication = authenticationHandler {
+ (disposition, credential) = onAuthentication(challenge)
+ }
+
+ completionHandler(disposition, credential)
+ }
+ }
+
+ public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
+ if let onError = didFinishWithErrors, let error = error {
+ if let nsError = error as NSError? {
+ if nsError.code == NSURLErrorCancelled {
+ // Do nothing
+ // Happens when view is disappearing and we cancelled
+ // ongoing HTTP request
+ return
+ }
+ }
+ onError(error)
+ }
+ }
+
+ // MARK: - Private function
+
+ fileprivate func executeBlock(block: @escaping () -> Void ) {
+ if imageView == nil {
+ block()
+ } else {
+ DispatchQueue.main.async { block() }
+ }
+ }
+}
diff --git a/OctoPod Mac/MJPEG Streaming/MjpegStreamingKit.h b/OctoPod Mac/MJPEG Streaming/MjpegStreamingKit.h
new file mode 100644
index 00000000..7152050b
--- /dev/null
+++ b/OctoPod Mac/MJPEG Streaming/MjpegStreamingKit.h
@@ -0,0 +1,19 @@
+//
+// MjpegStreamingKit.h
+// MjpegStreamingKit
+//
+// Created by Stefano Vettor on 28/03/16.
+// Copyright © 2016 Stefano Vettor. All rights reserved.
+//
+
+#import
+
+//! Project version number for MjpegStreamingKit.
+FOUNDATION_EXPORT double MjpegStreamingKitVersionNumber;
+
+//! Project version string for MjpegStreamingKit.
+FOUNDATION_EXPORT const unsigned char MjpegStreamingKitVersionString[];
+
+// In this header, you should import all the public headers of your framework using statements like #import
+
+
diff --git a/OctoPod Mac/Octopod_Mac.entitlements b/OctoPod Mac/Octopod_Mac.entitlements
new file mode 100644
index 00000000..1da788e4
--- /dev/null
+++ b/OctoPod Mac/Octopod_Mac.entitlements
@@ -0,0 +1,8 @@
+
+
+
+
+ com.apple.developer.aps-environment
+ development
+
+
diff --git a/OctoPod Mac/PopoverViewController.swift b/OctoPod Mac/PopoverViewController.swift
new file mode 100644
index 00000000..60c89e0c
--- /dev/null
+++ b/OctoPod Mac/PopoverViewController.swift
@@ -0,0 +1,448 @@
+//
+// ViewController.swift
+// OctoPod Mac
+//
+// Created by Arijit Banerjee on 6/27/20.
+// Copyright © 2020 Gaston Dombiak. All rights reserved.
+//
+
+import Cocoa
+import CoreData
+
+class PopoverViewController: NSViewController, OctoPrintClientDelegate, PreferencesDelegate, OctoPrintSettingsDelegate {
+ @IBOutlet weak var cameraImageView: CameraImageView!
+ @IBOutlet weak var connectButton: NSButton!
+
+ @IBOutlet weak var printerStatusLabel: NSTextField!
+
+ @IBOutlet weak var printerStatusValue: NSTextField!
+
+ @IBOutlet weak var actualExtruderTempValue: NSTextField!
+ @IBOutlet weak var targetExtruderTempValue: NSTextField!
+ @IBOutlet weak var actualBedTempValue: NSTextField!
+ @IBOutlet weak var targetBedTempValue: NSTextField!
+
+ @IBOutlet weak var progressPrintTimeValue: NSTextField!
+ @IBOutlet weak var progressPrintTimeLeftValue: NSTextField!
+ @IBOutlet weak var progressPrintCompletionValue: NSTextField!
+
+ @IBOutlet weak var progressPercentValue: NSTextField!
+ @IBOutlet weak var progressBar: NSProgressIndicator!
+
+ @IBOutlet weak var cancelButton: NSButton!
+ @IBOutlet weak var pauseResumeButton: NSButton!
+ @IBOutlet weak var extruderTempProgressBar: NSProgressIndicator!
+ @IBOutlet weak var bedTempProgressBar: NSProgressIndicator!
+
+ @IBOutlet weak var printerNameLabel: NSTextField!
+
+ @IBOutlet weak var octoprintWebButton: NSButton!
+
+ private var serverConnected = false
+ private var printerConnected: Bool?
+ private var isPrinting = false
+ private var isPaused = false
+ var streamingController: MjpegStreamingController?
+ private var lastEventReceivedAt = NSDate().timeIntervalSince1970
+
+ let printerManager: PrinterManager = { return (NSApp.delegate as! AppDelegate).printerManager! }()
+
+
+
+ lazy var octoPrintClient: OctoPrintClient = {
+ let octoPrintClient = OctoPrintClient(printerManager: self.printerManager)
+ octoPrintClient.delegates.append(self)
+ octoPrintClient.octoPrintSettingsDelegates.append(self)
+ return octoPrintClient
+ }()
+
+
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ DispatchQueue.main.async {
+ self.bedTempProgressBar.doubleValue = 0.0
+ self.extruderTempProgressBar.doubleValue = 0.0
+ }
+ connectToServer()
+ _ = Timer.scheduledTimer(withTimeInterval: 10.0, repeats: true) { timer in
+ if(!self.serverConnected){
+ NSLog("Server is not in connect state for \(NSDate().timeIntervalSince1970-self.lastEventReceivedAt) seconds")
+ self.onStaleReconnect(checkConnection: false)
+ }else{
+ NSLog("Server is conneccted state but no data received for \(NSDate().timeIntervalSince1970-self.lastEventReceivedAt) seconds")
+ self.onStaleReconnect(checkConnection: true)
+ }
+ }
+ }
+
+ private func onStaleReconnect(checkConnection: Bool){
+ if let defaultPrinter = printerManager.getDefaultPrinter()
+ {
+ if(checkConnection){
+ let isConnected = octoPrintClient.webSocketClient?.isConnected(printer: defaultPrinter)
+ if(!isConnected!){
+ disconnectFromServer()
+ connectToServer()
+ }
+ }else{
+ disconnectFromServer()
+ connectToServer()
+ }
+ }
+ }
+
+ fileprivate func octoPrintCameraAbsoluteUrl(hostname: String, streamUrl: String) -> String {
+ if streamUrl.isEmpty {
+ // Should never happen but let's be cautious
+ return hostname
+ }
+ if streamUrl.starts(with: "/") {
+ // Build absolute URL from relative URL
+ return hostname + streamUrl
+ }
+ // streamURL is an absolute URL so return it
+ return streamUrl
+ }
+ func updatePrinterStatusView(printerStatus:String?,actualExtruderTemp:Double?,targetExtruderTemp:Double?,actualBedTemp:Double?,targetBedTemp:Double?,progressPrintTime:Int?,progressPrintTimeLeft:Int?, progressCompletion:Double?){
+ actualBedTempValue.doubleValue = actualBedTemp ?? actualBedTempValue.doubleValue
+ targetBedTempValue.doubleValue = targetBedTemp ?? targetBedTempValue.doubleValue
+ printerStatusValue.stringValue = printerStatus ?? "Unknown"
+ actualExtruderTempValue.doubleValue = actualExtruderTemp ?? actualExtruderTempValue.doubleValue
+ targetExtruderTempValue.doubleValue = targetExtruderTemp ?? targetExtruderTempValue.doubleValue
+ progressBar.doubleValue = progressCompletion ?? progressBar.doubleValue
+ progressPercentValue.doubleValue = progressCompletion?.round(to: 1) ?? progressPercentValue.doubleValue
+ let progressPrintTimeLeftDouble = Double(progressPrintTimeLeft ?? 0)
+ progressPrintTimeValue.stringValue = UIUtils.secondsToPrintTime(seconds: progressPrintTime ?? 0)
+ progressPrintTimeLeftValue.stringValue = UIUtils.secondsToEstimatedPrintTime(seconds: progressPrintTimeLeftDouble)
+ progressPrintCompletionValue.stringValue = UIUtils.secondsToETA(seconds: progressPrintTimeLeft ?? 0)
+ progressBar.appearance = NSAppearance(named: .vibrantLight)
+
+ bedTempProgressBar.minValue = 10
+ bedTempProgressBar.maxValue = targetBedTempValue.doubleValue < 1.0 ? 100.0 : targetBedTempValue.doubleValue
+ bedTempProgressBar.doubleValue = actualBedTempValue.doubleValue
+ bedTempProgressBar.appearance = NSAppearance(named: .vibrantLight)
+
+ extruderTempProgressBar.minValue = 10
+ extruderTempProgressBar.maxValue = targetExtruderTempValue.doubleValue < 1.0 ? 250.0 : targetExtruderTempValue.doubleValue
+ extruderTempProgressBar.doubleValue = actualExtruderTempValue.doubleValue
+ extruderTempProgressBar.appearance = NSAppearance(named: .vibrantLight)
+ }
+
+ override var representedObject: Any? {
+ didSet {
+ // Update the view, if already loaded.
+ }
+ }
+
+
+ fileprivate func updateConnectButton(printerConnected: Bool, assumption: Bool) {
+ DispatchQueue.main.async {
+ if !printerConnected {
+ self.printerConnected = false
+ self.connectButton.title = NSLocalizedString("Connect", comment: "")
+ } else {
+ self.printerConnected = true
+ self.connectButton.title = NSLocalizedString("Disconnect", comment: "")
+ }
+ // Only enable button if we are sure about connection state
+ self.connectButton.isEnabled = !assumption
+ }
+ }
+
+ fileprivate func connectToServer() {
+ if let defaultPrinter = printerManager.getDefaultPrinter()
+ {
+ NSLog("Connecting to server")
+ DispatchQueue.main.async {
+ self.octoprintWebButton.isEnabled = !defaultPrinter.hostname.isEmpty
+ }
+ if !self.serverConnected{
+ if (!UIUtils.isValidURL(urlString: defaultPrinter.hostname)){
+ UIUtils.showAlert(title: "Invalid URL", message: "\(defaultPrinter.hostname) is not a valid URL. A valid URL should start with http:// or https://")
+ return
+ }
+ octoPrintClient.connectToServer(printer : defaultPrinter)
+ printerNameLabel.stringValue = defaultPrinter.name
+
+ }
+ }
+
+ }
+ private func connectCamera(printer:Printer){
+ if(serverConnected){
+ NSLog("Connecting camera")
+ if let imageView = self.cameraImageView {
+ DispatchQueue.main.async {
+ imageView.isHidden = false
+ }
+ let degrees = [0,90,180,270]
+ imageView.rotate(byDegrees: CGFloat(degrees[Int(printer.cameraOrientation)]))
+ streamingController = MjpegStreamingController(imageView: imageView)
+ if let defaultPrinterStreamURLString = printer.streamUrl{
+ let streamUrl = URL(string:octoPrintCameraAbsoluteUrl(hostname:printer.hostname, streamUrl: defaultPrinterStreamURLString))
+ streamingController?.play(url: streamUrl!)
+
+ }
+ }
+ }
+
+ }
+ private func disconnectFromServer(){
+ octoPrintClient.disconnectFromServer()
+ NSLog("Disconnecting from server")
+ serverConnected = false
+ streamingController?.stop()
+ DispatchQueue.main.async {
+ self.updatePrinterStatusView(
+ printerStatus: "???" ,
+ actualExtruderTemp: 0.0 ,
+ targetExtruderTemp: 0.0 ,
+ actualBedTemp: 0.0 ,
+ targetBedTemp: 0.0,
+ progressPrintTime: 0,
+ progressPrintTimeLeft: 0,
+ progressCompletion: 0.0
+ )
+ }
+ self.cameraImageView.isHidden = true
+ self.updateConnectButton(printerConnected: false, assumption: true)
+ }
+
+ func notificationAboutToConnectToServer() {
+ //NSLog("*************")
+ }
+
+
+ func printerStateUpdated(event: CurrentStateEvent) {
+ lastEventReceivedAt = NSDate().timeIntervalSince1970
+ DispatchQueue.main.async {
+ //Do UI Code here.
+ if let closed = event.closedOrError {
+ self.updateConnectButton(printerConnected: !closed, assumption: false)
+ }
+ }
+ if let isPrinting = event.printing {
+ self.isPrinting = event.printing!
+ DispatchQueue.main.async {
+ if(isPrinting){
+ self.cancelButton.isEnabled = true
+ self.pauseResumeButton.isEnabled = true
+ if #available(OSX 10.14, *) {
+ self.cancelButton.contentTintColor = .systemRed
+ self.pauseResumeButton.contentTintColor = .systemYellow
+ } else {
+ // Fallback on earlier versions
+ }
+ self.pauseResumeButton.title = "Pause"
+ }else{
+ self.cancelButton.isEnabled = false
+ self.pauseResumeButton.isEnabled = false
+ if #available(OSX 10.14, *) {
+ self.cancelButton.contentTintColor = .systemGray
+ self.pauseResumeButton.contentTintColor = .systemGray
+ } else {
+ // Fallback on earlier versions
+ }
+
+ }
+ }
+ }
+ if let isPaused = event.paused {
+ self.isPaused = event.paused!
+ DispatchQueue.main.async {
+ if(isPaused){
+ self.pauseResumeButton.isEnabled = true
+ self.cancelButton.isEnabled = true
+ if #available(OSX 10.14, *) {
+ self.cancelButton.contentTintColor = .systemRed
+ self.pauseResumeButton.contentTintColor = .systemGreen
+ } else {
+ // Fallback on earlier versions
+ }
+
+ self.pauseResumeButton.title = "Resume"
+ }
+ }
+ }
+ if let isPausing = event.pausing {
+ DispatchQueue.main.async {
+ if(isPausing){
+ self.pauseResumeButton.isEnabled = false
+ self.cancelButton.isEnabled = false
+ if #available(OSX 10.14, *) {
+ self.pauseResumeButton.contentTintColor = .systemGray
+ self.cancelButton.contentTintColor = .systemGray
+ } else {
+ // Fallback on earlier versions
+ }
+ self.pauseResumeButton.title = "Resume"
+ }
+ }
+ }
+
+ if let isCancelling = event.cancelling {
+ DispatchQueue.main.async {
+ if(isCancelling){
+ self.pauseResumeButton.isEnabled = false
+ self.cancelButton.isEnabled = false
+ if #available(OSX 10.14, *) {
+ self.cancelButton.contentTintColor = .systemGray
+ self.pauseResumeButton.contentTintColor = .systemGray
+ } else {
+ // Fallback on earlier versions
+ }
+
+ self.pauseResumeButton.title = "Pause"
+ }
+
+ }
+ }
+
+ self.updatePrinterStatusView(
+ printerStatus: event.state ,
+ actualExtruderTemp: event.tool0TempActual ,
+ targetExtruderTemp: event.tool0TempTarget ,
+ actualBedTemp: event.bedTempActual ,
+ targetBedTemp: event.bedTempTarget,
+ progressPrintTime: event.progressPrintTime,
+ progressPrintTimeLeft: event.progressPrintTimeLeft,
+ progressCompletion: event.progressCompletion
+ )
+ self.cameraImageView.setPrinerDetails(printerStatus: event.state ,
+ actualExtruderTemp: event.tool0TempActual ,
+ targetExtruderTemp: event.tool0TempTarget ,
+ actualBedTemp: event.bedTempActual ,
+ targetBedTemp: event.bedTempTarget,
+ progressPrintTime: event.progressPrintTime,
+ progressPrintTimeLeft: event.progressPrintTimeLeft,
+ progressCompletion: event.progressCompletion)
+
+ }
+
+ func handleConnectionError(error: Error?, response: HTTPURLResponse) {
+ NSLog("ERROR - handleConnectionError")
+ self.serverConnected = false
+ }
+
+ func websocketConnected() {
+ NSLog("WS Connected")
+ lastEventReceivedAt = NSDate().timeIntervalSince1970
+ if #available(OSX 10.14, *) {
+ UIUtils.notifyUser(title: "OctoPod",message: "OctoPod is connected to OctoPrint")
+ } else {
+ // Fallback on earlier versions
+ }
+ self.serverConnected = true
+ if let defaultPrinter = printerManager.getDefaultPrinter()
+ {
+ connectCamera(printer: defaultPrinter)
+ }
+ }
+
+ func websocketConnectionFailed(error: Error) {
+ NSLog("ERROR - websocketConnectionFailed")
+ disconnectFromServer()
+ self.serverConnected = false
+ connectToServer()
+ }
+ func printerAdded(printer: Printer) {
+ NSLog("printer added")
+ connectToServer()
+ }
+
+ func printerDeleted(printer: Printer) {
+ NSLog("printer deleted")
+ streamingController?.contentURL = URL(string: "")
+ streamingController?.stop()
+ disconnectFromServer()
+ }
+
+ func printerUpdated(printer: Printer) {
+ NSLog("printer updated")
+ disconnectFromServer()
+ connectToServer()
+ }
+
+ func cameraOrientationChanged(newOrientation: Int) {
+ if let imageView = cameraImageView {
+ imageView.rotate(byDegrees: CGFloat(newOrientation))
+ }
+ }
+
+ func cameraPathChanged(streamUrl: String) {
+ NSLog("Camera path changed. new URL \(streamUrl)")
+ if let defaultPrinter = printerManager.getDefaultPrinter()
+ {
+ connectCamera(printer: defaultPrinter)
+ }
+ }
+ @IBAction func toggleConnection(_ sender: NSButton) {
+ if printerConnected! {
+ self.octoPrintClient.disconnectFromPrinter { (requested: Bool, error: Error?, response: HTTPURLResponse) in
+ if requested {
+ DispatchQueue.main.async {
+ self.actualBedTempValue.doubleValue = 0.0
+ self.actualExtruderTempValue.doubleValue = 0.0
+ self.targetExtruderTempValue.doubleValue = 0.0
+ self.targetBedTempValue.doubleValue = 0.0
+ }
+ }
+ }
+ }else{
+ self.octoPrintClient.connectToPrinter{ (requested: Bool, error: Error?, response: HTTPURLResponse) in
+ if requested {
+ // add stuff
+ }
+ }
+ }
+ }
+
+ @IBAction func togglePauseResume(_ sender: Any) {
+ pauseResumeButton.isEnabled = false
+ if(self.isPaused){
+ self.octoPrintClient.resumeCurrentJob { (request:Bool, error: Error?, response:HTTPURLResponse) in
+ NSLog("Resumed")
+ }
+ }
+ else if(self.isPrinting){
+ let sure = UIUtils.showConfirm(title: "Confirmation", message: "Do you really want to pause this print?")
+ if(!sure){
+ return
+ }
+ self.octoPrintClient.pauseCurrentJob { (request:Bool, error: Error?, response:HTTPURLResponse) in
+ NSLog("Paused")
+ }
+ }
+
+ }
+
+ @IBAction func cancelNSLog(_ sender: Any) {
+ let sure = UIUtils.showConfirm(title: "Confirmation", message: "Do you really want to cancel this print?")
+ if(!sure){
+ return
+ }
+ cancelButton.isEnabled = false
+ if(self.isPrinting || self.isPaused){
+ self.octoPrintClient.cancelCurrentJob { (request:Bool, error: Error?, response:HTTPURLResponse) in
+ if #available(OSX 10.14, *) {
+ UIUtils.notifyUser(title: "OctoPod",message: "Print Cancelled")
+ } else {
+ // Fallback on earlier versions
+ }
+ NSLog("Cancelled")
+ }
+ }
+ }
+ @IBAction func openOctoprintWebsite(_ sender: Any) {
+ if let defaultPrinter = printerManager.getDefaultPrinter()
+ {
+ NSWorkspace.shared.open(URL(string: defaultPrinter.hostname)!)
+ }
+ }
+ @IBAction func openPreferences(_ sender: Any) {
+ (NSApp.delegate as! AppDelegate).showPreferences()
+ }
+
+}
+
diff --git a/OctoPod Mac/PreferencesDelegate.swift b/OctoPod Mac/PreferencesDelegate.swift
new file mode 100644
index 00000000..8474e696
--- /dev/null
+++ b/OctoPod Mac/PreferencesDelegate.swift
@@ -0,0 +1,15 @@
+//
+// PreferencesDelegate.swift
+// OctoPod Mac
+//
+// Created by Arijit Banerjee on 7/4/20.
+// Copyright © 2020 Gaston Dombiak. All rights reserved.
+//
+
+import Foundation
+protocol PreferencesDelegate: class {
+ func printerAdded(printer: Printer)
+ func printerDeleted(printer: Printer)
+ func printerUpdated(printer: Printer)
+ func cameraOrientationChanged(newOrientation: Int)
+}
diff --git a/OctoPod Mac/PreferencesViewController.swift b/OctoPod Mac/PreferencesViewController.swift
new file mode 100644
index 00000000..87d1ef93
--- /dev/null
+++ b/OctoPod Mac/PreferencesViewController.swift
@@ -0,0 +1,155 @@
+//
+// PreferencesViewController.swift
+// OctoPod Mac
+//
+// Created by Arijit Banerjee on 7/2/20.
+// Copyright © 2020 Gaston Dombiak. All rights reserved.
+//
+
+import Foundation
+import Cocoa
+import CoreData
+
+class PreferencesViewController: NSViewController{
+
+ @IBOutlet var printerNameValue : NSTextField!
+ @IBOutlet weak var printerHostNameValue: NSTextField!
+ @IBOutlet weak var octoPrintAPITokenValue: NSTextField!
+ @IBOutlet weak var ignoreSSLValue: NSButton!
+ @IBOutlet weak var updatePrinterButton: NSButton!
+ @IBOutlet weak var cameraOrientationValue: NSPopUpButton!
+ weak var delegate: PreferencesDelegate?
+
+ let printerManager: PrinterManager = { return (NSApp.delegate as! AppDelegate).printerManager! }()
+
+ override func viewDidLoad() {
+
+ super.viewDidLoad()
+ if let defaultPrinter = printerManager.getDefaultPrinter()
+ {
+ printerHostNameValue.stringValue = defaultPrinter.hostname
+ octoPrintAPITokenValue.stringValue = defaultPrinter.apiKey
+ printerNameValue.stringValue = defaultPrinter.name
+ cameraOrientationValue.selectItem(at: Int(defaultPrinter.cameraOrientation))
+ ignoreSSLValue.state = defaultPrinter.ignoreSSLCertValidationError ? NSControl.StateValue.on : NSControl.StateValue.off
+ }
+ }
+
+ private func validateInput() -> Bool{
+ let apiKey = octoPrintAPITokenValue.stringValue
+ let hostname = printerHostNameValue.stringValue
+ let printerName = printerNameValue.stringValue
+ if(apiKey.isEmpty || hostname.isEmpty || printerName.isEmpty){
+ return false
+ }
+ if (!UIUtils.isValidURL(urlString: hostname)){
+ return false
+ }
+ return true
+ }
+
+
+ @IBAction func addUpdatePrinter(_ sender: Any) {
+ if(!validateInput()){
+ UIUtils.showAlert(title: "Validation error", message: "Please check printer details. Fields cannot be empty. Make sure your OctoPrint hostname starts with a protocol(http:// or https://)")
+ return
+ }
+ let apiKey = octoPrintAPITokenValue.stringValue
+ let hostname = printerHostNameValue.stringValue
+ let printerName = printerNameValue.stringValue
+ if let defaultPrinter = printerManager.getDefaultPrinter()
+ //existing printer
+ {
+ defaultPrinter.apiKey = apiKey
+ defaultPrinter.hostname = hostname
+ defaultPrinter.name = printerName
+ defaultPrinter.defaultPrinter = true
+ defaultPrinter.ignoreSSLCertValidationError = ignoreSSLValue.state == .on ? true: false
+ printerManager.updatePrinter(defaultPrinter)
+ if let listener = delegate {
+ listener.printerUpdated(printer: defaultPrinter)
+ }
+ UIUtils.showAlert(title: "Info", message: "Printer updated.")
+ self.view.window!.windowController!.close()
+
+ }else{
+ //new printer
+ let success = printerManager.addPrinter(name: printerName, hostname: hostname, apiKey: apiKey, username: nil, password: nil, position: 0, iCloudUpdate: false)
+ if(success){
+ if let defaultPrinter = printerManager.getDefaultPrinter(){
+ defaultPrinter.ignoreSSLCertValidationError = ignoreSSLValue.state == .on ? true: false
+ printerManager.updatePrinter(defaultPrinter)
+ if let listener = delegate {
+ listener.printerAdded(printer: defaultPrinter)
+ }
+ UIUtils.showAlert(title: "Info", message: "Printer added.")
+ self.view.window!.windowController!.close()
+ }
+ else{
+ UIUtils.showAlert(title: "Error", message: "Cannot add printer")
+
+ }
+ }
+
+ }
+
+ }
+
+ @IBAction func resetPrinters(_ sender: Any) {
+ let confirmation = UIUtils.showConfirm(title: "Are you sure?", message: "This will delete all printers registered to OctoPod.")
+
+ if(confirmation){
+ if let defaultPrinter = printerManager.getDefaultPrinter(){
+ let newObjectContext = printerManager.newPrivateContext()
+ printerManager.deleteAllPrinters(context: newObjectContext)
+
+ if let listener = delegate {
+ listener.printerDeleted(printer: defaultPrinter)
+ }
+ printerHostNameValue.stringValue = ""
+ octoPrintAPITokenValue.stringValue = ""
+ printerNameValue.stringValue = ""
+ ignoreSSLValue.state = NSControl.StateValue.off
+
+ UIUtils.showAlert(title: "Done", message: "All Printers Deleted")
+ }
+
+ }
+
+ }
+
+ @IBAction func hostURLChanged(_ sender: Any) {
+ let inputURL = printerHostNameValue.stringValue
+ // Add http protocol to URL if no protocol was specified
+ if !inputURL.lowercased().starts(with: "http") {
+ printerHostNameValue.stringValue = "http://" + inputURL
+ }
+ //updatePrinterButton.isEnabled = validateInput()
+
+ }
+
+ @IBAction func printerNameChanged(_ sender: Any) {
+ //updatePrinterButton.isEnabled = validateInput()
+ }
+
+ @IBAction func apiTokenChanged(_ sender: Any) {
+ //updatePrinterButton.isEnabled = validateInput()
+ }
+ @IBAction func gotoGithub(_ sender: NSButton) {
+ NSWorkspace.shared.open(URL(string: sender.title)!)
+ }
+
+ @IBAction func cameraOrientationChanged(_ sender: NSPopUpButton) {
+ let index = sender.indexOfSelectedItem
+ let degrees = [0,90,180,270]
+ if let listener = delegate {
+ listener.cameraOrientationChanged(newOrientation: degrees[index])
+ }
+ NSLog("New orientation = \(degrees[index])")
+ if let defaultPrinter = printerManager.getDefaultPrinter(){
+ defaultPrinter.cameraOrientation = Int16(index)
+ printerManager.updatePrinter(defaultPrinter)
+ }
+ }
+
+}
diff --git a/OctoPod Mac/en.lproj/Main.storyboard b/OctoPod Mac/en.lproj/Main.storyboard
new file mode 100644
index 00000000..c71e81fc
--- /dev/null
+++ b/OctoPod Mac/en.lproj/Main.storyboard
@@ -0,0 +1,723 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/OctoPod.xcodeproj/project.pbxproj b/OctoPod.xcodeproj/project.pbxproj
index 87d18b76..827c4d31 100644
--- a/OctoPod.xcodeproj/project.pbxproj
+++ b/OctoPod.xcodeproj/project.pbxproj
@@ -292,6 +292,50 @@
583827DE22E36B9100784706 /* Palette2ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 583827DD22E36B9100784706 /* Palette2ViewController.swift */; };
583827E122E62D8700784706 /* Palette2PortsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 583827E022E62D8700784706 /* Palette2PortsViewController.swift */; };
5843B64A22E9FA1D001EDBAC /* PingPongHistoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5843B64922E9FA1D001EDBAC /* PingPongHistoryViewController.swift */; };
+ DE1F9E6B24A7299B00A609B7 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE1F9E6A24A7299B00A609B7 /* AppDelegate.swift */; };
+ DE1F9E6D24A7299B00A609B7 /* PopoverViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE1F9E6C24A7299B00A609B7 /* PopoverViewController.swift */; };
+ DE1F9E7824A72D0F00A609B7 /* OctoPrintRESTClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DAD2DDB21816D62004A4223 /* OctoPrintRESTClient.swift */; };
+ DE1F9E7924A72D3400A609B7 /* CustomControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D1A935621326AEF0050ED9A /* CustomControl.swift */; };
+ DE1F9E7A24A72D3400A609B7 /* ExecuteControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D1A936B213316850050ED9A /* ExecuteControl.swift */; };
+ DE1F9E7B24A72D3400A609B7 /* Container.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D1A935C2132709E0050ED9A /* Container.swift */; };
+ DE1F9E7C24A72D3400A609B7 /* Command.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D1A935821326B3C0050ED9A /* Command.swift */; };
+ DE1F9E7D24A72D3400A609B7 /* Script.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D1A935E213271680050ED9A /* Script.swift */; };
+ DE1F9E7E24A72D3400A609B7 /* ControlInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D1A935A21326C630050ED9A /* ControlInput.swift */; };
+ DE1F9E7F24A72D3400A609B7 /* SystemCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D8AB6A221A7390B00E81CE5 /* SystemCommand.swift */; };
+ DE1F9E8024A72D5200A609B7 /* IPPlug.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DAD2DE421817B23004A4223 /* IPPlug.swift */; };
+ DE1F9E8124A72D6B00A609B7 /* HTTPClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D8B292F20E5549B00296D9D /* HTTPClient.swift */; };
+ DE1F9E8224A72D7D00A609B7 /* CancelObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D88C3E82193F49500BC84DD /* CancelObject.swift */; };
+ DE1F9E8324A72DC500A609B7 /* Palette2Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DFB776F23D4D46100ADF315 /* Palette2Utils.swift */; };
+ DE1F9E8424A7DCA800A609B7 /* Printer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D82873620E3EBC80035AE0B /* Printer.swift */; };
+ DE1F9E8524A7DCC200A609B7 /* EnclosureInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D6F74512443FB8A006847B6 /* EnclosureInput.swift */; };
+ DE1F9E8624A7DCC600A609B7 /* EnclosureOutput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DB932C924475B7700A154FF /* EnclosureOutput.swift */; };
+ DE1F9E8724A7DD4000A609B7 /* CurrentStateEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DBAE4A020E69D4900D2137D /* CurrentStateEvent.swift */; };
+ DE1F9E8824A7DD5100A609B7 /* PrintFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D8873F720F534C400DCE987 /* PrintFile.swift */; };
+ DE1F9E8E24A7DEDB00A609B7 /* Starscream in Frameworks */ = {isa = PBXBuildFile; productRef = DE1F9E8D24A7DEDB00A609B7 /* Starscream */; };
+ DE1F9E9024A7E02300A609B7 /* TempHistory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D92E6C721213B2400F58BA5 /* TempHistory.swift */; };
+ DE1F9E9124A7E0DF00A609B7 /* WebSocketClientDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DBAE4A220E6A02A00D2137D /* WebSocketClientDelegate.swift */; };
+ DE1F9E9224A7E4F100A609B7 /* WebSocketClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D98C1D920E5D096007BB13F /* WebSocketClient.swift */; };
+ DE1F9E9424A7ED7300A609B7 /* OctoPrintClientDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D4508A220F956AD00AC0EC8 /* OctoPrintClientDelegate.swift */; };
+ DE1F9E9524A7EF3B00A609B7 /* Plugins.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DE3FE6C2154B9CE000D7D2A /* Plugins.swift */; };
+ DE1F9E9624A7EF4100A609B7 /* OctoPrintPluginsDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D071A13212682C1005301B4 /* OctoPrintPluginsDelegate.swift */; };
+ DE1F9E9724A7EF4800A609B7 /* PrinterProfilesDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DFA806F210E1A8C00B5BFAA /* PrinterProfilesDelegate.swift */; };
+ DE1F9E9A24A7F0BC00A609B7 /* Terminal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D78BA9E21039D770090D79A /* Terminal.swift */; };
+ DE1F9E9D24A7F29800A609B7 /* ComplicationContentType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D5D614723D57C2000D988A2 /* ComplicationContentType.swift */; };
+ DE1F9EA124A814D300A609B7 /* PrinterManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D82873820E3EC3B0035AE0B /* PrinterManager.swift */; };
+ DE1F9EA224A81A5400A609B7 /* OctoPod.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 3D45280820E15A370094CCB7 /* OctoPod.xcdatamodeld */; };
+ DE1F9EA324A81CC700A609B7 /* SharedPersistentContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D4497D321B230BE00736F0F /* SharedPersistentContainer.swift */; };
+ DE1F9EA424A8284F00A609B7 /* OctoPrintSettingsDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D5849E3210CC8720068D87A /* OctoPrintSettingsDelegate.swift */; };
+ DE1F9EA624A829F300A609B7 /* OctoPrintClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D8B293120E556D400296D9D /* OctoPrintClient.swift */; };
+ DE1F9EAB24A82F6B00A609B7 /* AppConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE1F9EA924A82F6B00A609B7 /* AppConfiguration.swift */; };
+ DE1F9EAC24A82F6B00A609B7 /* AppConfigurationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE1F9EAA24A82F6B00A609B7 /* AppConfigurationDelegate.swift */; };
+ DE1F9EBB24A87E3B00A609B7 /* MjpegStreamingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE1F9EB924A87E3A00A609B7 /* MjpegStreamingController.swift */; };
+ DE1F9EC024A9CB0700A609B7 /* CameraImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE1F9EBF24A9CB0700A609B7 /* CameraImageView.swift */; };
+ DE1F9EC324ABEAC200A609B7 /* UIUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D33B7A0213F6A0D002A4BE3 /* UIUtils.swift */; };
+ DE1F9EC524ABFB6600A609B7 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE1F9EC424ABFB6600A609B7 /* Extensions.swift */; };
+ DE1F9EC824AEEF7E00A609B7 /* PreferencesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE1F9EC724AEEF7E00A609B7 /* PreferencesViewController.swift */; };
+ DEE3256F24B06B45000375BD /* PreferencesDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEE3256E24B06B45000375BD /* PreferencesDelegate.swift */; };
+ DEE3257424B3CAEF000375BD /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DE1F9E6E24A7299C00A609B7 /* Assets.xcassets */; };
+ DEF8D00324B58B6B0027E2A9 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DE1F9E7024A7299C00A609B7 /* Main.storyboard */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -726,6 +770,21 @@
CB66535C2160B6C200269730 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/LaunchScreen.strings; sourceTree = ""; };
CB66535D2160B6C200269730 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; };
CB66535E2160B6C200269730 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/InfoPlist.strings; sourceTree = ""; };
+ DE1F9E6824A7299B00A609B7 /* OctoPod Mac.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "OctoPod Mac.app"; sourceTree = BUILT_PRODUCTS_DIR; };
+ DE1F9E6A24A7299B00A609B7 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
+ DE1F9E6C24A7299B00A609B7 /* PopoverViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopoverViewController.swift; sourceTree = ""; };
+ DE1F9E6E24A7299C00A609B7 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = "OctoPod Mac/Assets.xcassets"; sourceTree = SOURCE_ROOT; };
+ DE1F9E7324A7299C00A609B7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ DE1F9EA924A82F6B00A609B7 /* AppConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AppConfiguration.swift; path = "OctoPod TV/AppConfiguration.swift"; sourceTree = SOURCE_ROOT; };
+ DE1F9EAA24A82F6B00A609B7 /* AppConfigurationDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AppConfigurationDelegate.swift; path = "OctoPod TV/AppConfigurationDelegate.swift"; sourceTree = SOURCE_ROOT; };
+ DE1F9EB924A87E3A00A609B7 /* MjpegStreamingController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MjpegStreamingController.swift; sourceTree = ""; };
+ DE1F9EBA24A87E3A00A609B7 /* MjpegStreamingKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MjpegStreamingKit.h; sourceTree = ""; };
+ DE1F9EBE24A9AF4E00A609B7 /* Octopod_Mac.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Octopod_Mac.entitlements; sourceTree = ""; };
+ DE1F9EBF24A9CB0700A609B7 /* CameraImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraImageView.swift; sourceTree = ""; };
+ DE1F9EC424ABFB6600A609B7 /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; };
+ DE1F9EC724AEEF7E00A609B7 /* PreferencesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesViewController.swift; sourceTree = ""; };
+ DEE3256E24B06B45000375BD /* PreferencesDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesDelegate.swift; sourceTree = ""; };
+ DEF8D00424B58E4A0027E2A9 /* en */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = en; path = en.lproj/Main.storyboard; sourceTree = ""; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -791,6 +850,14 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ DE1F9E6524A7299B00A609B7 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ DE1F9E8E24A7DEDB00A609B7 /* Starscream in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
@@ -930,6 +997,7 @@
3DABB21E22C0044D00F32731 /* OctoPod Notification Extension */,
3D53713D240100FC00B28067 /* OctoPod TV */,
3D537155240100FD00B28067 /* OctoPod TVUITests */,
+ DE1F9E6924A7299B00A609B7 /* OctoPod Mac */,
3D4527FF20E15A370094CCB7 /* Products */,
2AF93107507B57283430D6F0 /* Frameworks */,
60CAB181B212B9D16A9EA3E1 /* Pods */,
@@ -947,6 +1015,7 @@
3DABB21D22C0044D00F32731 /* OctoPod Notification Extension.appex */,
3D53713C240100FC00B28067 /* OctoPod TV.app */,
3D537152240100FD00B28067 /* OctoPod TVUITests.xctest */,
+ DE1F9E6824A7299B00A609B7 /* OctoPod Mac.app */,
);
name = Products;
sourceTree = "";
@@ -1367,6 +1436,35 @@
path = Pods;
sourceTree = "";
};
+ DE1F9E6924A7299B00A609B7 /* OctoPod Mac */ = {
+ isa = PBXGroup;
+ children = (
+ DE1F9EB824A87E2100A609B7 /* MJPEG Streaming */,
+ DE1F9EA924A82F6B00A609B7 /* AppConfiguration.swift */,
+ DE1F9EAA24A82F6B00A609B7 /* AppConfigurationDelegate.swift */,
+ DE1F9E6A24A7299B00A609B7 /* AppDelegate.swift */,
+ DE1F9E6C24A7299B00A609B7 /* PopoverViewController.swift */,
+ DE1F9E6E24A7299C00A609B7 /* Assets.xcassets */,
+ DE1F9E7024A7299C00A609B7 /* Main.storyboard */,
+ DE1F9EBE24A9AF4E00A609B7 /* Octopod_Mac.entitlements */,
+ DE1F9E7324A7299C00A609B7 /* Info.plist */,
+ DE1F9EBF24A9CB0700A609B7 /* CameraImageView.swift */,
+ DE1F9EC424ABFB6600A609B7 /* Extensions.swift */,
+ DE1F9EC724AEEF7E00A609B7 /* PreferencesViewController.swift */,
+ DEE3256E24B06B45000375BD /* PreferencesDelegate.swift */,
+ );
+ path = "OctoPod Mac";
+ sourceTree = "";
+ };
+ DE1F9EB824A87E2100A609B7 /* MJPEG Streaming */ = {
+ isa = PBXGroup;
+ children = (
+ DE1F9EB924A87E3A00A609B7 /* MjpegStreamingController.swift */,
+ DE1F9EBA24A87E3A00A609B7 /* MjpegStreamingKit.h */,
+ );
+ path = "MJPEG Streaming";
+ sourceTree = "";
+ };
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@@ -1521,13 +1619,33 @@
productReference = 3DABB21D22C0044D00F32731 /* OctoPod Notification Extension.appex */;
productType = "com.apple.product-type.app-extension";
};
+ DE1F9E6724A7299B00A609B7 /* OctoPod Mac */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = DE1F9E7724A7299C00A609B7 /* Build configuration list for PBXNativeTarget "OctoPod Mac" */;
+ buildPhases = (
+ DE1F9E6424A7299B00A609B7 /* Sources */,
+ DE1F9E6524A7299B00A609B7 /* Frameworks */,
+ DE1F9E6624A7299B00A609B7 /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = "OctoPod Mac";
+ packageProductDependencies = (
+ DE1F9E8D24A7DEDB00A609B7 /* Starscream */,
+ );
+ productName = "OctoPod Mac";
+ productReference = DE1F9E6824A7299B00A609B7 /* OctoPod Mac.app */;
+ productType = "com.apple.product-type.application";
+ };
/* End PBXNativeTarget section */
/* Begin PBXProject section */
3D4527F620E15A370094CCB7 /* Project object */ = {
isa = PBXProject;
attributes = {
- LastSwiftUpdateCheck = 1130;
+ LastSwiftUpdateCheck = 1150;
LastUpgradeCheck = 0940;
ORGANIZATIONNAME = "Gaston Dombiak";
TargetAttributes = {
@@ -1587,6 +1705,9 @@
3DABB21C22C0044D00F32731 = {
CreatedOnToolsVersion = 10.2.1;
};
+ DE1F9E6724A7299B00A609B7 = {
+ CreatedOnToolsVersion = 11.5;
+ };
};
};
buildConfigurationList = 3D4527F920E15A370094CCB7 /* Build configuration list for PBXProject "OctoPod" */;
@@ -1624,6 +1745,7 @@
3DABB21C22C0044D00F32731 /* OctoPod Notification Extension */,
3D53713B240100FC00B28067 /* OctoPod TV */,
3D537151240100FD00B28067 /* OctoPod TVUITests */,
+ DE1F9E6724A7299B00A609B7 /* OctoPod Mac */,
);
};
/* End PBXProject section */
@@ -1707,6 +1829,15 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ DE1F9E6624A7299B00A609B7 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ DEF8D00324B58B6B0027E2A9 /* Main.storyboard in Resources */,
+ DEE3257424B3CAEF000375BD /* Assets.xcassets in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
@@ -2011,6 +2142,54 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ DE1F9E6424A7299B00A609B7 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ DE1F9E9624A7EF4100A609B7 /* OctoPrintPluginsDelegate.swift in Sources */,
+ DE1F9E9224A7E4F100A609B7 /* WebSocketClient.swift in Sources */,
+ DE1F9E7E24A72D3400A609B7 /* ControlInput.swift in Sources */,
+ DE1F9E9124A7E0DF00A609B7 /* WebSocketClientDelegate.swift in Sources */,
+ DE1F9EA224A81A5400A609B7 /* OctoPod.xcdatamodeld in Sources */,
+ DE1F9E7A24A72D3400A609B7 /* ExecuteControl.swift in Sources */,
+ DE1F9EA124A814D300A609B7 /* PrinterManager.swift in Sources */,
+ DE1F9E8124A72D6B00A609B7 /* HTTPClient.swift in Sources */,
+ DE1F9E7F24A72D3400A609B7 /* SystemCommand.swift in Sources */,
+ DE1F9EAC24A82F6B00A609B7 /* AppConfigurationDelegate.swift in Sources */,
+ DE1F9E7B24A72D3400A609B7 /* Container.swift in Sources */,
+ DE1F9E9524A7EF3B00A609B7 /* Plugins.swift in Sources */,
+ DE1F9E6D24A7299B00A609B7 /* PopoverViewController.swift in Sources */,
+ DE1F9E8424A7DCA800A609B7 /* Printer.swift in Sources */,
+ DE1F9E8224A72D7D00A609B7 /* CancelObject.swift in Sources */,
+ DE1F9E8724A7DD4000A609B7 /* CurrentStateEvent.swift in Sources */,
+ DE1F9E7824A72D0F00A609B7 /* OctoPrintRESTClient.swift in Sources */,
+ DE1F9EA424A8284F00A609B7 /* OctoPrintSettingsDelegate.swift in Sources */,
+ DE1F9E8324A72DC500A609B7 /* Palette2Utils.swift in Sources */,
+ DE1F9EC324ABEAC200A609B7 /* UIUtils.swift in Sources */,
+ DE1F9EAB24A82F6B00A609B7 /* AppConfiguration.swift in Sources */,
+ DE1F9E9024A7E02300A609B7 /* TempHistory.swift in Sources */,
+ DE1F9E9724A7EF4800A609B7 /* PrinterProfilesDelegate.swift in Sources */,
+ DE1F9E7D24A72D3400A609B7 /* Script.swift in Sources */,
+ DE1F9E8824A7DD5100A609B7 /* PrintFile.swift in Sources */,
+ DE1F9E9A24A7F0BC00A609B7 /* Terminal.swift in Sources */,
+ DEE3256F24B06B45000375BD /* PreferencesDelegate.swift in Sources */,
+ DE1F9E7C24A72D3400A609B7 /* Command.swift in Sources */,
+ DE1F9E7924A72D3400A609B7 /* CustomControl.swift in Sources */,
+ DE1F9E6B24A7299B00A609B7 /* AppDelegate.swift in Sources */,
+ DE1F9E9424A7ED7300A609B7 /* OctoPrintClientDelegate.swift in Sources */,
+ DE1F9E8624A7DCC600A609B7 /* EnclosureOutput.swift in Sources */,
+ DE1F9E8024A72D5200A609B7 /* IPPlug.swift in Sources */,
+ DE1F9E9D24A7F29800A609B7 /* ComplicationContentType.swift in Sources */,
+ DE1F9EA624A829F300A609B7 /* OctoPrintClient.swift in Sources */,
+ DE1F9EC524ABFB6600A609B7 /* Extensions.swift in Sources */,
+ DE1F9EC824AEEF7E00A609B7 /* PreferencesViewController.swift in Sources */,
+ DE1F9E8524A7DCC200A609B7 /* EnclosureInput.swift in Sources */,
+ DE1F9EC024A9CB0700A609B7 /* CameraImageView.swift in Sources */,
+ DE1F9EBB24A87E3B00A609B7 /* MjpegStreamingController.swift in Sources */,
+ DE1F9EA324A81CC700A609B7 /* SharedPersistentContainer.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
@@ -2291,6 +2470,14 @@
name = Localizable.strings;
sourceTree = "";
};
+ DE1F9E7024A7299C00A609B7 /* Main.storyboard */ = {
+ isa = PBXVariantGroup;
+ children = (
+ DEF8D00424B58E4A0027E2A9 /* en */,
+ );
+ name = Main.storyboard;
+ sourceTree = "";
+ };
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
@@ -2799,6 +2986,55 @@
};
name = Release;
};
+ DE1F9E7524A7299C00A609B7 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-Mac";
+ CODE_SIGN_ENTITLEMENTS = "OctoPod Mac/OctoPod_Mac.entitlements";
+ CODE_SIGN_IDENTITY = "Apple Development";
+ CODE_SIGN_STYLE = Automatic;
+ COMBINE_HIDPI_IMAGES = YES;
+ DEVELOPMENT_TEAM = QCEM4H639D;
+ ENABLE_HARDENED_RUNTIME = YES;
+ INFOPLIST_FILE = "OctoPod Mac/Info.plist";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/../Frameworks",
+ );
+ MACOSX_DEPLOYMENT_TARGET = 10.13;
+ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+ MTL_FAST_MATH = YES;
+ PRODUCT_BUNDLE_IDENTIFIER = "org.OctoPod.OctoPod-Mac";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SDKROOT = macosx;
+ SWIFT_VERSION = 5.0;
+ };
+ name = Debug;
+ };
+ DE1F9E7624A7299C00A609B7 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-Mac";
+ CODE_SIGN_ENTITLEMENTS = "OctoPod Mac/OctoPod_Mac.entitlements";
+ CODE_SIGN_IDENTITY = "Apple Development";
+ CODE_SIGN_STYLE = Automatic;
+ COMBINE_HIDPI_IMAGES = YES;
+ DEVELOPMENT_TEAM = QCEM4H639D;
+ ENABLE_HARDENED_RUNTIME = YES;
+ INFOPLIST_FILE = "OctoPod Mac/Info.plist";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/../Frameworks",
+ );
+ MACOSX_DEPLOYMENT_TARGET = 10.13;
+ MTL_FAST_MATH = YES;
+ PRODUCT_BUNDLE_IDENTIFIER = "org.OctoPod.OctoPod-Mac";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SDKROOT = macosx;
+ SWIFT_VERSION = 5.0;
+ };
+ name = Release;
+ };
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
@@ -2883,6 +3119,15 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
+ DE1F9E7724A7299C00A609B7 /* Build configuration list for PBXNativeTarget "OctoPod Mac" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ DE1F9E7524A7299C00A609B7 /* Debug */,
+ DE1F9E7624A7299C00A609B7 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
/* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
@@ -2920,6 +3165,11 @@
package = 3D2BEA6C248B328400EF6859 /* XCRemoteSwiftPackageReference "Starscream" */;
productName = Starscream;
};
+ DE1F9E8D24A7DEDB00A609B7 /* Starscream */ = {
+ isa = XCSwiftPackageProductDependency;
+ package = 3D2BEA6C248B328400EF6859 /* XCRemoteSwiftPackageReference "Starscream" */;
+ productName = Starscream;
+ };
/* End XCSwiftPackageProductDependency section */
/* Begin XCVersionGroup section */
diff --git a/OctoPod/Info.plist b/OctoPod/Info.plist
index 2540ba0c..6dcdff88 100644
--- a/OctoPod/Info.plist
+++ b/OctoPod/Info.plist
@@ -8,6 +8,10 @@
OctoPod
CFBundleExecutable
$(EXECUTABLE_NAME)
+ CFBundleIcons
+
+ CFBundleIcons~ipad
+
CFBundleIdentifier
$(PRODUCT_BUNDLE_IDENTIFIER)
CFBundleInfoDictionaryVersion
diff --git a/OctoPod/Model/Printer.swift b/OctoPod/Model/Printer.swift
index dd6f788f..577f3db8 100644
--- a/OctoPod/Model/Printer.swift
+++ b/OctoPod/Model/Printer.swift
@@ -89,7 +89,9 @@ class Printer: NSManagedObject {
@NSManaged public var enclosureInputs: Set?
@NSManaged public var enclosureOutputs: Set?
-
+ /// whether to ignore SSL cert validation
+ @NSManaged var ignoreSSLCertValidationError: Bool
+
func getStreamPath() -> String {
if let path = streamUrl {
return path
diff --git a/OctoPod/Model/PrinterManager.swift b/OctoPod/Model/PrinterManager.swift
index de0bb0c4..890407a3 100644
--- a/OctoPod/Model/PrinterManager.swift
+++ b/OctoPod/Model/PrinterManager.swift
@@ -1,5 +1,10 @@
import Foundation
+#if canImport(UIKit)
+// iOS, tvOS, and watchOS – use UIKit
import UIKit
+#else
+// all other platforms meaning macOS
+#endif
import CoreData
// Manager of persistent printer information (OctoPrint servers) that are stored in the iPhone
@@ -108,7 +113,13 @@ class PrinterManager {
printer.iCloudUpdate = iCloudUpdate
printer.sdSupport = true // Assume that printer supports SD card. Will get updated later with actual value
+ #if canImport(UIKit)
+ // iOS, tvOS, and watchOS – use UIKit
printer.cameraOrientation = Int16(UIImage.Orientation.up.rawValue) // Assume no flips or rotations for camera. Will get updated later with actual value
+ #else
+ // all other platforms meaning macOS
+ #endif
+
printer.invertX = false // Assume control of X axis is not inverted. Will get updated later with actual value
printer.invertY = false // Assume control of Y axis is not inverted. Will get updated later with actual value
diff --git a/OctoPod/OctoPod.xcdatamodeld/OctoPod v10.xcdatamodel/contents b/OctoPod/OctoPod.xcdatamodeld/OctoPod v10.xcdatamodel/contents
index 7ece91e9..457123d2 100644
--- a/OctoPod/OctoPod.xcdatamodeld/OctoPod v10.xcdatamodel/contents
+++ b/OctoPod/OctoPod.xcdatamodeld/OctoPod v10.xcdatamodel/contents
@@ -29,6 +29,7 @@
+
@@ -63,6 +64,6 @@
-
+
\ No newline at end of file
diff --git a/OctoPod/OctoPrint/HTTPClient.swift b/OctoPod/OctoPrint/HTTPClient.swift
index fd402ed3..c7d7abfc 100644
--- a/OctoPod/OctoPrint/HTTPClient.swift
+++ b/OctoPod/OctoPrint/HTTPClient.swift
@@ -1,5 +1,5 @@
import Foundation
-import UIKit // Used for network indicator in the UI
+//import UIKit // Used for network indicator in the UI
// Basic HTTP Client that offers support for basic HTTP verbs.
// Authentication (user/password) is supported in case a reverse proxy
diff --git a/OctoPod/OctoPrint/OctoPrintClient.swift b/OctoPod/OctoPrint/OctoPrintClient.swift
index 36898f33..177bd3b2 100644
--- a/OctoPod/OctoPrint/OctoPrintClient.swift
+++ b/OctoPod/OctoPrint/OctoPrintClient.swift
@@ -1,5 +1,10 @@
import Foundation
+#if canImport(UIKit)
+// iOS, tvOS, and watchOS – use UIKit
import UIKit
+#else
+// all other platforms meaning macOS
+#endif
/// OctoPrint client that exposes the REST API described
/// here: http://docs.octoprint.org/en/master/api/index.html
@@ -21,15 +26,17 @@ class OctoPrintClient: WebSocketClientDelegate, AppConfigurationDelegate {
var octoPrintSettingsDelegates: Array = Array()
var printerProfilesDelegates: Array = Array()
var octoPrintPluginsDelegates: Array = Array()
-
+
var appConfiguration: AppConfiguration {
- get {
- let appDelegate = UIApplication.shared.delegate as! AppDelegate
- return appDelegate.appConfiguration
- }
- set(configuration) {
- configuration.delegates.append(self)
- }
+
+ get {
+// let appDelegate = UIApplication.shared.delegate as! AppDelegate
+// return appDelegate.appConfiguration
+ return AppDelegate().appConfiguration
+ }
+ set(configuration) {
+ configuration.delegates.append(self)
+ }
}
// Remember last CurrentStateEvent that was reported from OctoPrint (via websockets)
@@ -756,8 +763,10 @@ class OctoPrintClient: WebSocketClientDelegate, AppConfigurationDelegate {
}
}
}
-
+
if let webcam = json["webcam"] as? NSDictionary {
+ #if canImport(UIKit)
+ // iOS, tvOS, and watchOS – use UIKit
if let flipH = webcam["flipH"] as? Bool, let flipV = webcam["flipV"] as? Bool, let rotate90 = webcam["rotate90"] as? Bool {
let newOrientation = calculateImageOrientation(flipH: flipH, flipV: flipV, rotate90: rotate90)
if printer.cameraOrientation != newOrientation.rawValue {
@@ -772,6 +781,10 @@ class OctoPrintClient: WebSocketClientDelegate, AppConfigurationDelegate {
}
}
}
+ #else
+ // all other platforms meaning macOS
+ #endif
+
if let streamUrl = webcam["streamUrl"] as? String {
if printer.streamUrl != streamUrl {
// Update path to camera hosted by OctoPrint
@@ -1241,7 +1254,8 @@ class OctoPrintClient: WebSocketClientDelegate, AppConfigurationDelegate {
}
}
}
-
+ #if canImport(UIKit)
+ // iOS, tvOS, and watchOS – use UIKit
fileprivate func calculateImageOrientation(flipH: Bool, flipV: Bool, rotate90: Bool) -> UIImage.Orientation {
if !flipH && !flipV && !rotate90 {
// No flips selected
@@ -1269,6 +1283,10 @@ class OctoPrintClient: WebSocketClientDelegate, AppConfigurationDelegate {
return UIImage.Orientation.right
}
}
+ #else
+ // all other platforms meaning macOS
+ #endif
+
// MARK: - Private - Printer Profile functions
diff --git a/OctoPod/OctoPrint/OctoPrintSettingsDelegate.swift b/OctoPod/OctoPrint/OctoPrintSettingsDelegate.swift
index 5ad7364a..38d76115 100644
--- a/OctoPod/OctoPrint/OctoPrintSettingsDelegate.swift
+++ b/OctoPod/OctoPrint/OctoPrintSettingsDelegate.swift
@@ -1,12 +1,24 @@
import Foundation
+import Starscream
+#if canImport(UIKit)
+// iOS, tvOS, and watchOS – use UIKit
import UIKit
+#else
+// all other platforms meaning macOS
+#endif
/// Listener that reacts to changes in OctoPrint Settings (/api/settings)
/// This is done via the OctoPrint admin console
protocol OctoPrintSettingsDelegate: class {
/// Notification that orientation of the camera hosted by OctoPrint has changed
+
+ #if canImport(UIKit)
+ // iOS, tvOS, and watchOS – use UIKit
func cameraOrientationChanged(newOrientation: UIImage.Orientation)
+ #else
+ // all other platforms meaning macOS
+ #endif
/// Notification that path to camera hosted by OctoPrint has changed
func cameraPathChanged(streamUrl: String)
@@ -58,9 +70,15 @@ protocol OctoPrintSettingsDelegate: class {
// Make everything optional so implementors of this protocol are not forced to implement everything
extension OctoPrintSettingsDelegate {
-
+ #if canImport(UIKit)
+ // iOS, tvOS, and watchOS – use UIKit
func cameraOrientationChanged(newOrientation: UIImage.Orientation) {
}
+ #else
+ // all other platforms meaning macOS
+ #endif
+
+
func cameraPathChanged(streamUrl: String) {
}
diff --git a/OctoPod/OctoPrint/WebSocketClient.swift b/OctoPod/OctoPrint/WebSocketClient.swift
index a934b410..93f150f0 100644
--- a/OctoPod/OctoPrint/WebSocketClient.swift
+++ b/OctoPod/OctoPrint/WebSocketClient.swift
@@ -1,7 +1,12 @@
import Foundation
import Starscream
+#if canImport(UIKit)
+// iOS, tvOS, and watchOS – use UIKit
import UIKit
-
+#else
+// all other platforms meaning macOS
+import Cocoa
+#endif
// Classic websocket client that connects to "/sockjs/websocket"
// To receive socket events and received messages, create a WebSocketClientDelegate
// and add it as a delegate of this WebSocketClient
@@ -378,8 +383,16 @@ class WebSocketClient : NSObject, WebSocketAdvancedDelegate {
self.socket = WebSocket(request: self.socketRequest!)
// Configure if SSL certificate validation is disabled or not
DispatchQueue.main.async {
+ #if canImport(UIKit)
+ // iOS, tvOS, and watchOS – use UIKit
let appDelegate = UIApplication.shared.delegate as! AppDelegate
self.socket?.disableSSLCertValidation = appDelegate.appConfiguration.certValidationDisabled()
+ #else
+ // all other platforms meaning macOS
+ let printerManager = (NSApp.delegate as! AppDelegate).printerManager!
+ let ignoreSSLCertValidation = printerManager.getDefaultPrinter()?.ignoreSSLCertValidationError
+ self.socket?.disableSSLCertValidation = ignoreSSLCertValidation ?? false
+ #endif
}
}
diff --git a/OctoPod/UIUtils.swift b/OctoPod/UIUtils.swift
index 105c6f2c..8f913691 100644
--- a/OctoPod/UIUtils.swift
+++ b/OctoPod/UIUtils.swift
@@ -1,8 +1,16 @@
import Foundation
+import UserNotifications
+#if canImport(UIKit)
+// iOS, tvOS, and watchOS – use UIKit
import UIKit
+#else
+// all other platforms meaning macOS
+import Cocoa
+#endif
class UIUtils {
-
+ #if canImport(UIKit)
+ // iOS, tvOS, and watchOS – use UIKit
/// Caller may not be running in Main thread
static func showAlert(presenter: UIViewController, title: String, message: String, done: (() -> Void)?) {
// We are not always on the main thread so present dialog on main thread to prevent crashes
@@ -17,7 +25,20 @@ class UIUtils {
}
}
}
+ #else
+ // all other platforms meaning macOS
+ static func showAlert(title: String, message: String) {
+ let alert = NSAlert()
+ alert.messageText = title
+ alert.informativeText = message
+ alert.alertStyle = .warning
+ alert.addButton(withTitle: "OK")
+ alert.runModal()
+ }
+ #endif
+ #if canImport(UIKit)
+ // iOS, tvOS, and watchOS – use UIKit
/// Caller MUST be running in Main thread
static func showConfirm(presenter: UIViewController, message: String, yes: @escaping (UIAlertAction) -> Void, no: @escaping (UIAlertAction) -> Void) {
let alert = UIAlertController(title: NSLocalizedString("Confirm", comment: ""), message: message, preferredStyle: .alert)
@@ -28,6 +49,41 @@ class UIUtils {
// Nothing to do here
}
}
+ #else
+ // all other platforms meaning macOS
+ static func showConfirm(title: String, message: String) -> Bool {
+ let alert = NSAlert()
+ alert.messageText = title
+ alert.informativeText = message
+ alert.alertStyle = .warning
+ alert.addButton(withTitle: "OK")
+ alert.addButton(withTitle: "Cancel")
+ return alert.runModal() == .alertFirstButtonReturn
+ }
+ #endif
+ @available(OSX 10.14, *)
+ static func notifyUser(title: String, message: String) {
+ let content = UNMutableNotificationContent()
+ content.title = title
+ content.body = message
+ content.sound = UNNotificationSound.default
+ content.categoryIdentifier = "alarm"
+ // Configure the recurring date.
+ let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 2, repeats: false)
+
+ // Create the request
+ let uuidString = UUID().uuidString
+ let request = UNNotificationRequest(identifier: uuidString,
+ content: content, trigger: trigger)
+
+ // Schedule the request with the system.
+ let notificationCenter = UNUserNotificationCenter.current()
+ notificationCenter.add(request) { (error) in
+ if error != nil {
+ print(error.debugDescription)
+ }
+ }
+ }
static func calculateCameraHeightConstraints(screenHeight: CGFloat) -> (cameraHeight4_3ConstraintPortrait: CGFloat, cameraHeight4_3ConstraintLandscape: CGFloat, camera16_9HeightConstraintPortrait: CGFloat, cameral16_9HeightConstraintLandscape: CGFloat){
if screenHeight <= 568 {
@@ -103,7 +159,15 @@ class UIUtils {
formatter.allowedUnits = [ .day, .hour, .minute ]
return formatter.string(from: duration)!
}
-
+ /// Converts number of seconds into a string that represents time (e.g. 23h 10m)
+ static func secondsToPrintTime(seconds: Int) -> String {
+ let duration = TimeInterval(seconds)
+ let formatter = DateComponentsFormatter()
+ formatter.unitsStyle = .brief
+ formatter.allowedUnits = [ .day, .hour, .minute, .second ]
+ formatter.zeroFormattingBehavior = [ .default ]
+ return formatter.string(from: duration)!
+ }
/// Return estimated complection date based on number of estimated seconds to completion
/// - parameter seconds: estimated number of seconds to complection
static func secondsToETA(seconds: Int) -> String {
@@ -134,8 +198,22 @@ class UIUtils {
return ""
}
}
+ static func isValidURL(urlString: String) -> Bool {
+
+ let urlRegEx = "(http|https)://((\\w)*|([0-9]*)|([-|_]|[\\.|/])*)+(:[0-9]+)?"
+ let urlTest = NSPredicate(format: "SELF MATCHES %@", urlRegEx)
+ var result = urlTest.evaluate(with: urlString)
+ if !result {
+ let ipv6RegEx = "(http|https)://(\\[)?(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))(])?(:[0-9]+)?"
+ let ipv6Test = NSPredicate(format: "SELF MATCHES %@", ipv6RegEx)
+ result = ipv6Test.evaluate(with: urlString)
+ }
+ return result
+ }
}
+#if canImport(UIKit)
+// iOS, tvOS, and watchOS – use UIKit
extension UIImage {
func resizeWithWidth(width: CGFloat) -> UIImage? {
let imageView = UIImageView(frame: CGRect(origin: .zero, size: CGSize(width: width, height: CGFloat(ceil(width/size.width * size.height)))))
@@ -149,6 +227,9 @@ extension UIImage {
return result
}
}
+#else
+// all other platforms meaning macOS
+#endif
extension Date {
func timeAgoDisplay() -> String {