diff --git a/MSG Desktop.xcodeproj/project.pbxproj b/MSG Desktop.xcodeproj/project.pbxproj index 592e5b0..1526d40 100644 --- a/MSG Desktop.xcodeproj/project.pbxproj +++ b/MSG Desktop.xcodeproj/project.pbxproj @@ -397,7 +397,7 @@ COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"MSG Desktop/Preview Content\""; - DEVELOPMENT_TEAM = C8Z9PRF4VH; + DEVELOPMENT_TEAM = RN94J52F92; ENABLE_HARDENED_RUNTIME = YES; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; @@ -424,7 +424,7 @@ COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"MSG Desktop/Preview Content\""; - DEVELOPMENT_TEAM = C8Z9PRF4VH; + DEVELOPMENT_TEAM = RN94J52F92; ENABLE_HARDENED_RUNTIME = YES; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; diff --git a/MSG Desktop/ContentView.swift b/MSG Desktop/ContentView.swift index 0f145da..1d0b216 100644 --- a/MSG Desktop/ContentView.swift +++ b/MSG Desktop/ContentView.swift @@ -7,131 +7,131 @@ import SwiftUI +struct ViewHeightKey: PreferenceKey { + static var defaultValue: CGFloat = 0 + static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { + value = nextValue() + } +} + struct ContentView: View { @ObservedObject var appState = AppState.shared private let maxLines = 300 @State private var showCancelButton: Bool = false @State private var showReinitPopup = false @State private var showCancelPopup = false + @State private var imageHeight: CGFloat = 0 var body: some View { - VStack { -// Image(systemName: "globe") -// .imageScale(.large) -// .foregroundStyle(.tint) - let appIcon = NSImage(named: NSImage.applicationIconName) + GeometryReader { geometry in + VStack(spacing: 10) { + // App icon + let appIcon = NSImage(named: NSImage.applicationIconName) + Image(nsImage: appIcon ?? NSImage()) + .resizable() + .scaledToFit() + .frame(maxHeight: 150) + .background( + GeometryReader { proxy in + Color.clear + .preference(key: ViewHeightKey.self, value: proxy.size.height) + } + ) + .onPreferenceChange(ViewHeightKey.self) { height in + self.imageHeight = height + } - // 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: $appState.output) - .font(.system(.body, design: .monospaced)) - .frame(minWidth: 750, minHeight: 300) - .padding() - .disabled(true) - - Text("") - .id("bottom") + // ScrollView fills remaining height + ScrollViewReader { proxy in + ScrollView { + VStack(alignment: .leading) { + TextEditor(text: $appState.output) + .font(.system(.body, design: .monospaced)) + .frame( + width: geometry.size.width - 60, + height: max(0, geometry.size.height - imageHeight - 120) // - rough height of button row and padding + ) + .padding() + .disabled(true) + + Text("") + .id("bottom") + } + } + .onChange(of: appState.output) { _, _ in + trimLinesIfNeeded() + withAnimation { + proxy.scrollTo("bottom", anchor: .bottom) + } } } - .onChange(of: appState.output) { _, _ in - trimLinesIfNeeded() - withAnimation { - proxy.scrollTo("bottom", anchor: .bottom) + + // Buttons + HStack { + Button("Init") { + DispatchQueue.main.async { self.showCancelButton = true } + runShellCommandAsync(command: "msg machine init", outputText: $appState.output, showButton: $showCancelButton) } - } - } - HStack { - Button(action: { - DispatchQueue.main.async { - self.showCancelButton = true + + Button("Reinit") { + showReinitPopup.toggle() } - runShellCommandAsync(command: "msg machine init", outputText: $appState.output, showButton: $showCancelButton) - - }) { - Text("Init") - } - - - Button(action: { - showReinitPopup.toggle() - }) { - Text("Reinit") - }.confirmationDialog("Are you sure?", isPresented: $showReinitPopup, titleVisibility: .visible) { - Button("Confirm", role: .destructive) { - DispatchQueue.main.async { + .confirmationDialog("Are you sure?", isPresented: $showReinitPopup, titleVisibility: .visible) { + Button("Confirm", role: .destructive) { 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: $appState.output, showButton: nil) }) { - Text("Start") - } - if showCancelButton { - Button(action: { - 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: $appState.output, showButton: nil) }) { - Text("Stop") + + Button("Start") { + runShellCommandAsync(command: "msg machine start", outputText: $appState.output, showButton: nil) + } + + if showCancelButton { + Button("Cancel") { + showCancelPopup.toggle() + } + .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("Stop") { + runShellCommandAsync(command: "msg machine stop", outputText: $appState.output, showButton: nil) + } + } + + Button("Shell") { + runShellCommandAsync( + command: "osascript -e 'tell application \"Terminal\" to do script \"msg shell\"' -e 'tell application \"Terminal\" to activate'", + outputText: $appState.output, + showButton: nil + ) } } - - - Button(action: { - 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") - } } - - + .padding() + .frame(maxHeight: .infinity) + // Removed fixed frame here to allow dynamic window sizing } - .padding() - .frame(width: 800, height: 400) } - private func trimLinesIfNeeded() { - let lines = AppState.shared.output.split(separator: "\n") - - if lines.count > maxLines { - _ = lines.count - maxLines - let trimmedLines = lines.suffix(maxLines) - AppState.shared.output = trimmedLines.joined(separator: "\n") - } + let lines = AppState.shared.output.split(separator: "\n") + if lines.count > maxLines { + let trimmedLines = lines.suffix(maxLines) + AppState.shared.output = trimmedLines.joined(separator: "\n") } + } } #Preview { diff --git a/MSG Desktop/MSG_DesktopApp.swift b/MSG Desktop/MSG_DesktopApp.swift index 2b28241..8a29d11 100644 --- a/MSG Desktop/MSG_DesktopApp.swift +++ b/MSG Desktop/MSG_DesktopApp.swift @@ -59,12 +59,14 @@ func runShellCommandAsync(command: String, outputText: Binding?, showBut } class AppDelegate: NSObject, NSApplicationDelegate { + var window: NSWindow! + 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) } - + func saveAppState() { print("App is terminating. Saving state...") UserDefaults.standard.set(Date(), forKey: "lastClosed") @@ -73,44 +75,54 @@ class AppDelegate: NSObject, NSApplicationDelegate { var statusBarItem: NSStatusItem! func applicationDidFinishLaunching(_ notification: Notification) { - statusBarItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength) - - 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 + statusBarItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength) + + let contentView = ContentView() + window = NSWindow( + contentRect: NSRect(x: 0, y: 0, width: 800, height: 600), + styleMask: [.titled, .closable, .resizable, .miniaturizable], + backing: .buffered, + defer: false + ) + window.center() + //window.setFrameAutosaveName("MSG App") // to retain size after quitting + window.contentView = NSHostingView(rootView: contentView) + window.makeKeyAndOrderFront(nil) + + if let button = statusBarItem.button { + button.title = "MSG" + button.toolTip = "MSG App" } - @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 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) } + } } - - class AppState: ObservableObject { @Published var output: String = "Please select an option \n" - static let shared = AppState() + static let shared = AppState() } @main @@ -118,8 +130,6 @@ struct MSG_DesktopApp: App { @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate var body: some Scene { - WindowGroup { - ContentView() - } + Settings {} // prevents crash on some macOS version } }