diff --git a/MSG Desktop.xcodeproj/project.pbxproj b/MSG Desktop.xcodeproj/project.pbxproj index 1526d40..592e5b0 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 = RN94J52F92; + DEVELOPMENT_TEAM = C8Z9PRF4VH; 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 = RN94J52F92; + DEVELOPMENT_TEAM = C8Z9PRF4VH; ENABLE_HARDENED_RUNTIME = YES; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; diff --git a/MSG Desktop/Assets.xcassets/AppStatusBar.imageset/1024.png b/MSG Desktop/Assets.xcassets/AppStatusBar.imageset/1024.png deleted file mode 100644 index a3e22d5..0000000 Binary files a/MSG Desktop/Assets.xcassets/AppStatusBar.imageset/1024.png and /dev/null differ diff --git a/MSG Desktop/Assets.xcassets/AppStatusBar.imageset/512@1x.png b/MSG Desktop/Assets.xcassets/AppStatusBar.imageset/512@1x.png deleted file mode 100644 index 5fea9f7..0000000 Binary files a/MSG Desktop/Assets.xcassets/AppStatusBar.imageset/512@1x.png and /dev/null differ diff --git a/MSG Desktop/Assets.xcassets/AppStatusBar.imageset/512@2x.png b/MSG Desktop/Assets.xcassets/AppStatusBar.imageset/512@2x.png deleted file mode 100644 index a3e22d5..0000000 Binary files a/MSG Desktop/Assets.xcassets/AppStatusBar.imageset/512@2x.png and /dev/null differ diff --git a/MSG Desktop/Assets.xcassets/AppStatusBar.imageset/Contents.json b/MSG Desktop/Assets.xcassets/AppStatusBar.imageset/Contents.json deleted file mode 100644 index 14e7fc5..0000000 --- a/MSG Desktop/Assets.xcassets/AppStatusBar.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "filename" : "512@1x.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "512@2x.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "1024.png", - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/MSG Desktop/ContentView.swift b/MSG Desktop/ContentView.swift index dcaf585..0f145da 100644 --- a/MSG Desktop/ContentView.swift +++ b/MSG Desktop/ContentView.swift @@ -7,163 +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 { - 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 - } + VStack { +// 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 - ZStack { - Color.black - .ignoresSafeArea() - - // ScrollView fills remaining height - ScrollViewReader { proxy in - ScrollView { - LazyVStack(alignment: .leading, spacing: 0) { - let lines = appState.output.components(separatedBy: .newlines) - - ForEach(lines.indices, id: \.self) { index in - Text(lines[index]) - .font(.system(.body, design: .monospaced)) - .foregroundColor(.gray) - .frame(maxWidth: .infinity, alignment: .leading) - - } - - Color.clear.frame(height: 1).id("bottom") - } + + ScrollViewReader { proxy in + ScrollView { + VStack(alignment: .leading) { + TextEditor(text: $appState.output) + .font(.system(.body, design: .monospaced)) + .frame(minWidth: 750, minHeight: 300) .padding() - } - .onChange(of: appState.output) { _, _ in - trimLinesIfNeeded() - withAnimation { - proxy.scrollTo("bottom", anchor: .bottom) - } - } + .disabled(true) - // 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) - // } - // } + Text("") + .id("bottom") } } - - // Buttons - HStack { - Button("Init") { - DispatchQueue.main.async { self.showCancelButton = true } - runShellCommandAsync(command: "msg machine init", outputText: $appState.output, showButton: $showCancelButton) + .onChange(of: appState.output) { _, _ in + trimLinesIfNeeded() + withAnimation { + proxy.scrollTo("bottom", anchor: .bottom) } - - Button("Reinit") { - showReinitPopup.toggle() + } + } + HStack { + Button(action: { + DispatchQueue.main.async { + self.showCancelButton = true } - .confirmationDialog("Are you sure?", isPresented: $showReinitPopup, titleVisibility: .visible) { - Button("Confirm", role: .destructive) { + 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 { 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) {} } - - 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 - ) + } + + + if !showCancelButton{ + Button(action: { + 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: $appState.output, showButton: nil) + + }) { + Text("Shell") + } } - .padding() - .frame(maxHeight: .infinity) - .onAppear { - // Silences error due to metallib on resizing of window - let device = MTLCreateSystemDefaultDevice() - _ = device?.makeDefaultLibrary() - } + + } + .padding() + .frame(width: 800, height: 400) } + private func trimLinesIfNeeded() { - let lines = AppState.shared.output.split(separator: "\n") - if 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 { + _ = lines.count - maxLines + let trimmedLines = lines.suffix(maxLines) + AppState.shared.output = trimmedLines.joined(separator: "\n") + } } - } } #Preview { diff --git a/MSG Desktop/MSG_Desktop.metal b/MSG Desktop/MSG_Desktop.metal deleted file mode 100644 index 1635f22..0000000 --- a/MSG Desktop/MSG_Desktop.metal +++ /dev/null @@ -1,11 +0,0 @@ -// -// MGS_Desktop.metal -// MSG Desktop -// -// Created by Etienne Roesch on 31/03/2025. -// - -#include -using namespace metal; - - diff --git a/MSG Desktop/MSG_DesktopApp.swift b/MSG Desktop/MSG_DesktopApp.swift index 39d8c85..2b28241 100644 --- a/MSG Desktop/MSG_DesktopApp.swift +++ b/MSG Desktop/MSG_DesktopApp.swift @@ -34,7 +34,6 @@ func runShellCommandAsync(command: String, outputText: Binding?, showBut var observer: NSObjectProtocol? - observer = NotificationCenter.default.addObserver(forName: .NSFileHandleDataAvailable, object: fileHandle, queue: nil) { _ in let data = fileHandle.availableData if let output = String(data: data, encoding: .utf8), !output.isEmpty { @@ -60,14 +59,12 @@ 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") @@ -76,61 +73,44 @@ class AppDelegate: NSObject, NSApplicationDelegate { var statusBarItem: NSStatusItem! func applicationDidFinishLaunching(_ notification: Notification) { - 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" - if let image = NSImage(named: "AppStatusBar") { - let newSize = NSSize(width: 18, height: 18) - image.size = newSize - image.isTemplate = true // Important: so it adapts to dark/light mode but we need to get B&W icons.. - button.image = image + statusBarItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength) + + if let button = statusBarItem.button { + button.title = "MSG" + button.toolTip = "MSG App" } - button.target = self + + let menu = NSMenu() + + menu.addItem(NSMenuItem(title: "Quit", action: #selector(quitApp), keyEquivalent: "q")) + + statusBarItem.menu = menu } - 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) + @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 @@ -138,6 +118,8 @@ struct MSG_DesktopApp: App { @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate var body: some Scene { - Settings {} // prevents crash on some macOS version + WindowGroup { + ContentView() + } } } diff --git a/README.org b/README.org index c68dbb1..d520504 100644 --- a/README.org +++ b/README.org @@ -1,4 +1,4 @@ -[[./doc/images/init-example.gif]] +[[./example-gifs/init-example.gif]] * Install from tap ** Add Tap @@ -10,11 +10,3 @@ brew tap MSG/apps https://forge.superkamiguru.org/MSG/homebrew-apps #+begin_src sh brew install msg-desktop #+end_src - -** Security & Privacy -The first time you run the app, you will need to allow it to run in the Security -& Privacy settings of your Mac. This is because the app is not signed by Apple. -You can do this by going to System Preferences > Security & Privacy > General -and clicking "Open Anyway" next to the message about the app being blocked. - -[[./doc/images/macos15.3.2-security.png]] diff --git a/doc/.DS_Store b/doc/.DS_Store deleted file mode 100644 index 5008ddf..0000000 Binary files a/doc/.DS_Store and /dev/null differ diff --git a/doc/images/macos15.3.2-security.png b/doc/images/macos15.3.2-security.png deleted file mode 100644 index 923b4e8..0000000 Binary files a/doc/images/macos15.3.2-security.png and /dev/null differ diff --git a/doc/images/init-example.gif b/example-gifs/init-example.gif similarity index 100% rename from doc/images/init-example.gif rename to example-gifs/init-example.gif