From a1a414ce838bef44d2d06d85947bc0a0a55e7a47 Mon Sep 17 00:00:00 2001
From: Chad Nelson <cnelson@mailbox.org>
Date: Sun, 9 Mar 2025 07:27:37 -0600
Subject: [PATCH] updated a lot of stuff

---
 MSG Desktop/ContentView.swift    | 80 +++++++++++++++++++--------
 MSG Desktop/MSG_DesktopApp.swift | 94 +++++++++++++++++++-------------
 2 files changed, 113 insertions(+), 61 deletions(-)

diff --git a/MSG Desktop/ContentView.swift b/MSG Desktop/ContentView.swift
index 0d5af2f..0f145da 100644
--- a/MSG Desktop/ContentView.swift	
+++ b/MSG Desktop/ContentView.swift	
@@ -8,18 +8,29 @@
 import SwiftUI
 
 struct ContentView: View {
-    @State public var output: String = "Please select an option \n"
+    @ObservedObject var appState = AppState.shared
     private let maxLines = 300
-    @State private var showCancelButton: Bool = false 
+    @State private var showCancelButton: Bool = false
+    @State private var showReinitPopup = false
+    @State private var showCancelPopup = false
+
     var body: some View {
         VStack {
-            Image(systemName: "globe")
-                .imageScale(.large)
-                .foregroundStyle(.tint)
+//            Image(systemName: "globe")
+//                .imageScale(.large)
+//                .foregroundStyle(.tint)
+            let appIcon = NSImage(named: NSImage.applicationIconName)
+
+                    // Use SwiftUI's Image to display the icon
+            Image(nsImage: appIcon ?? NSImage())
+                .resizable() // Optional: if you want to resize the icon
+                .scaledToFit() // Optional: makes sure the aspect ratio is maintained
+                
+            
             ScrollViewReader { proxy in
                 ScrollView {
                     VStack(alignment: .leading) {
-                        TextEditor(text: $output)
+                        TextEditor(text: $appState.output)
                             .font(.system(.body, design: .monospaced))
                             .frame(minWidth: 750, minHeight: 300)
                             .padding()
@@ -29,7 +40,7 @@ struct ContentView: View {
                             .id("bottom")
                     }
                 }
-                .onChange(of: output) { _, _ in
+                .onChange(of: appState.output) { _, _ in
                     trimLinesIfNeeded()
                     withAnimation {
                         proxy.scrollTo("bottom", anchor: .bottom)
@@ -41,41 +52,64 @@ struct ContentView: View {
                     DispatchQueue.main.async {
                         self.showCancelButton = true
                     }
-                    runShellCommandAsync(command: "msg machine init", outputText: $output, showButton: $showCancelButton)
+                    runShellCommandAsync(command: "msg machine init", outputText: $appState.output, showButton: $showCancelButton)
                      
                 }) {
                     Text("Init")
                 }
+                
+                
                 Button(action: {
-                    DispatchQueue.main.async {
-                        self.showCancelButton = true
-                    }
-                    runShellCommandAsync(command: "rm -rf ~/.guix && msg machine init", outputText: $output, showButton: nil)                }) {
+                    showReinitPopup.toggle()
+                                    }) {
                     Text("Reinit")
+                }.confirmationDialog("Are you sure?", isPresented: $showReinitPopup, titleVisibility: .visible) {
+                    Button("Confirm", role: .destructive) {
+                        DispatchQueue.main.async {
+                            self.showCancelButton = true
+                            runShellCommandAsync(command: "rm -rf ~/.guix && msg machine init", outputText: $appState.output, showButton: $showCancelButton)
+                        }
+                        
+                    }
+                    Button("Cancel", role: .cancel) {}
                 }
+                
+                
                 Button(action: {
-                    runShellCommandAsync(command: "msg machine start", outputText: $output, showButton: nil)                }) {
+                    runShellCommandAsync(command: "msg machine start", outputText: $appState.output, showButton: nil)                }) {
                     Text("Start")
                 }
                 if showCancelButton {
                     Button(action: {
-                        runShellCommandAsync(command: "kill $(ps aux | grep '[g]uile' | awk '{print $2}')", outputText: $output, showButton: nil)
-                        runShellCommandAsync(command: "kill $(ps aux | grep '[q]emu' | awk '{print $2}')", outputText: $output, showButton: nil)
-                        DispatchQueue.main.async {
-                            self.showCancelButton = false
-                        }}) {
+                        showCancelPopup.toggle()
+                        }) {
                         Text("Cancel")
+                    }.confirmationDialog("Are you sure?", isPresented: $showCancelPopup, titleVisibility: .visible) {
+                        Button("Confirm", role: .destructive) {
+            
+                            runShellCommandAsync(command: "kill $(ps aux | grep '[g]uile' | awk '{print $2}')", outputText: $appState.output, showButton: nil)
+                            runShellCommandAsync(command: "kill $(ps aux | grep '[q]emu' | awk '{print $2}')", outputText: $appState.output, showButton: nil)
+                
+                            DispatchQueue.main.async {
+                                
+                                self.showCancelButton = false
+                            }
+                        }
+                        Button("Cancel", role: .cancel) {}
                     }
                 }
+                
+                
                 if !showCancelButton{
                     Button(action: {
-                        runShellCommandAsync(command: "msg machine stop", outputText: $output, showButton: nil)                }) {
+                        runShellCommandAsync(command: "msg machine stop", outputText: $appState.output, showButton: nil)                }) {
                         Text("Stop")
                     }
                 }
                 
+                
                 Button(action: {
-                    runShellCommandAsync(command: "osascript -e 'tell application \"Terminal\" to do script \"msg shell\"' -e 'tell application \"Terminal\" to activate'", outputText: $output, showButton: nil)
+                    runShellCommandAsync(command: "osascript -e 'tell application \"Terminal\" to do script \"msg shell\"' -e 'tell application \"Terminal\" to activate'", outputText: $appState.output, showButton: nil)
                     
                 }) {
                     Text("Shell")
@@ -90,12 +124,12 @@ struct ContentView: View {
 
     
     private func trimLinesIfNeeded() {
-            let lines = output.split(separator: "\n")
+            let lines = AppState.shared.output.split(separator: "\n")
             
             if lines.count > maxLines {
                 _ = lines.count - maxLines
-                let trimmedLines = lines.suffix(maxLines) // Keep only the last 300 lines
-                output = trimmedLines.joined(separator: "\n") // Join back to a single string
+                let trimmedLines = lines.suffix(maxLines)
+                AppState.shared.output = trimmedLines.joined(separator: "\n")
             }
         }
 }
diff --git a/MSG Desktop/MSG_DesktopApp.swift b/MSG Desktop/MSG_DesktopApp.swift
index 9fe5e55..2b28241 100644
--- a/MSG Desktop/MSG_DesktopApp.swift	
+++ b/MSG Desktop/MSG_DesktopApp.swift	
@@ -6,25 +6,10 @@
 //
 
 import SwiftUI
+import AppKit
 import Foundation
 
-func oldshell(_ command: String) -> String {
-    let task = Process()
-    let pipe = Pipe()
-    
-    task.standardOutput = pipe
-    task.standardError = pipe
-    task.arguments = ["-c", command]
-    task.launchPath = "/bin/zsh"
-    task.standardInput = nil
-    task.launch()
-    
-    let data = pipe.fileHandleForReading.readDataToEndOfFile()
-    let output = String(data: data, encoding: .utf8)!
-    return output
-}
-
-func runShellCommandAsync(command: String, outputText: Binding<String>, showButton: Binding<Bool>?) {
+func runShellCommandAsync(command: String, outputText: Binding<String>?, showButton: Binding<Bool>?) {
     DispatchQueue.global(qos: .background).async {
         let task = Process()
         let pipe = Pipe()
@@ -53,7 +38,9 @@ func runShellCommandAsync(command: String, outputText: Binding<String>, showButt
             let data = fileHandle.availableData
             if let output = String(data: data, encoding: .utf8), !output.isEmpty {
                 DispatchQueue.main.async {
-                    outputText.wrappedValue += output
+                    if let outputText = outputText {
+                        outputText.wrappedValue += output
+                    }
                 }
             }
             fileHandle.waitForDataInBackgroundAndNotify()
@@ -71,34 +58,65 @@ func runShellCommandAsync(command: String, outputText: Binding<String>, showButt
     }
 }
 
-func shell(_ command: String) -> String {
-    let task = Process()
-    let pipe = Pipe()
-    
-    task.standardOutput = pipe
-    task.standardError = pipe
-    task.arguments = ["-c", command]
-    task.launchPath = "/bin/zsh"
-    task.standardInput = nil
+class AppDelegate: NSObject, NSApplicationDelegate {
+    func applicationWillTerminate(_ notification: Notification) {
+        runShellCommandAsync(command: "msg machine stop", outputText: nil, showButton: nil)
+        runShellCommandAsync(command: "kill $(ps aux | grep '[g]uile' | awk '{print $2}')", outputText: nil, showButton: nil)
+        runShellCommandAsync(command: "kill $(ps aux | grep '[q]emu' | awk '{print $2}')", outputText: nil, showButton: nil)
+    }
 
-    task.environment = [
-        "GUILE_LOAD_PATH": "/opt/homebrew/share/guile/site/3.0:/usr/local/share/guile/site/3.0/",
-        "GUILE_LOAD_COMPILED_PATH": "/opt/homebrew/lib/guile/3.0/site-ccache:/usr/local/lib/guile/3.0/site-ccache/",
-        "GUILE_SYSTEM_EXTENSIONS_PATH": "/opt/homebrew/lib/guile/3.0/extensions:/usr/local/lib/guile/3.0/extensions/",
-        "PATH": "$PATH:/opt/homebrew/bin:/usr/bin:/usr/local/bin:/bin"
-    ]
+    func saveAppState() {
+        print("App is terminating. Saving state...")
+        UserDefaults.standard.set(Date(), forKey: "lastClosed")
+    }
+    
+    var statusBarItem: NSStatusItem!
+    
+    func applicationDidFinishLaunching(_ notification: Notification) {
+            statusBarItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
 
-    task.launch()
+            if let button = statusBarItem.button {
+                button.title = "MSG"
+                button.toolTip = "MSG App"
+            }
+            
+            let menu = NSMenu()
+            
+            menu.addItem(NSMenuItem(title: "Quit", action: #selector(quitApp), keyEquivalent: "q"))
+            
+            statusBarItem.menu = menu
+        }
+        
+        @objc func quitApp() {
+            let alert = NSAlert()
+            alert.messageText = "Are you sure you want to quit?"
+            alert.informativeText = "Do you want to quit the app and exit?"
+            alert.alertStyle = .warning
+            
+            alert.addButton(withTitle: "Quit")
+            alert.addButton(withTitle: "Cancel")
+            
+            let response = alert.runModal()
+            
+            if response == .alertFirstButtonReturn {
+                NSApplication.shared.terminate(nil)
+            }
+        }
     
-    let data = pipe.fileHandleForReading.readDataToEndOfFile()
-    let output = String(data: data, encoding: .utf8) ?? "Error reading output"
-    
-    return output.trimmingCharacters(in: .whitespacesAndNewlines)
 }
 
 
+
+class AppState: ObservableObject {
+    @Published var output: String = "Please select an option \n"
+    
+    static let shared = AppState() 
+}
+
 @main
 struct MSG_DesktopApp: App {
+    @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
+    
     var body: some Scene {
         WindowGroup {
             ContentView()