mirror of
https://github.com/nicoverbruggen/phpmon.git
synced 2025-08-07 12:00:09 +02:00
391 lines
13 KiB
Swift
391 lines
13 KiB
Swift
//
|
|
// MainMenu.swift
|
|
// PHP Monitor
|
|
//
|
|
// Copyright © 2021 Nico Verbruggen. All rights reserved.
|
|
//
|
|
|
|
import Cocoa
|
|
|
|
class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate {
|
|
|
|
static let shared = MainMenu()
|
|
|
|
weak var menuDelegate: NSMenuDelegate? = nil
|
|
|
|
/**
|
|
The status bar item with variable length.
|
|
*/
|
|
let statusItem = NSStatusBar.system.statusItem(
|
|
withLength: NSStatusItem.variableLength
|
|
)
|
|
|
|
// MARK: - UI related
|
|
|
|
/**
|
|
Kick off the startup of the rendering of the main menu.
|
|
*/
|
|
func startup() {
|
|
// Start with the icon
|
|
setStatusBar(image: NSImage(named: NSImage.Name("StatusBarIcon"))!)
|
|
|
|
// Perform environment boot checks
|
|
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
|
|
Startup().checkEnvironment(success: { onEnvironmentPass() },
|
|
failure: { onEnvironmentFail() }
|
|
)
|
|
}
|
|
}
|
|
|
|
/**
|
|
When the environment is all clear and the app can run, let's go.
|
|
*/
|
|
private func onEnvironmentPass() {
|
|
_ = Actions.detectPhpVersions()
|
|
|
|
if HomebrewDiagnostics.shared.errors.contains(.aliasConflict) {
|
|
DispatchQueue.main.async {
|
|
Alert.notify(
|
|
message: "alert.php_alias_conflict.title".localized,
|
|
info: "alert.php_alias_conflict.info".localized,
|
|
style: .critical
|
|
)
|
|
}
|
|
}
|
|
|
|
updatePhpVersionInStatusBar()
|
|
|
|
// Attempt to find out if PHP-FPM is broken
|
|
let installation = App.phpInstall!
|
|
installation.notifyAboutBrokenPhpFpm()
|
|
|
|
// Attempt to find out more info about Valet
|
|
let valet = Valet()
|
|
print("PHP Monitor has extracted the version number of Valet: \(valet.version)")
|
|
|
|
// Schedule a request to fetch the PHP version every 60 seconds
|
|
DispatchQueue.main.async { [self] in
|
|
App.shared.timer = Timer.scheduledTimer(
|
|
timeInterval: 60,
|
|
target: self,
|
|
selector: #selector(updatePhpVersionInStatusBar),
|
|
userInfo: nil,
|
|
repeats: true
|
|
)
|
|
}
|
|
}
|
|
|
|
/**
|
|
When the environment is not OK, present an alert to inform the user.
|
|
*/
|
|
private func onEnvironmentFail() {
|
|
DispatchQueue.main.async { [self] in
|
|
let close = Alert.present(
|
|
messageText: "alert.cannot_start.title".localized,
|
|
informativeText: "alert.cannot_start.info".localized,
|
|
buttonTitle: "alert.cannot_start.close".localized,
|
|
secondButtonTitle: "alert.cannot_start.retry".localized
|
|
)
|
|
|
|
if (close) {
|
|
exit(1)
|
|
}
|
|
|
|
startup()
|
|
}
|
|
}
|
|
|
|
/**
|
|
Update the menu's contents, based on what's going on.
|
|
*/
|
|
func update() {
|
|
// Update the menu item on the main thread
|
|
DispatchQueue.main.async { [self] in
|
|
// Create a new menu
|
|
let menu = StatusMenu()
|
|
|
|
// Add the PHP versions (or error messages)
|
|
menu.addPhpVersionMenuItems()
|
|
menu.addItem(NSMenuItem.separator())
|
|
|
|
// Add the possible actions
|
|
menu.addPhpActionMenuItems()
|
|
menu.addItem(NSMenuItem.separator())
|
|
|
|
// Add information about services & actions
|
|
menu.addPhpConfigurationMenuItems()
|
|
menu.addItem(NSMenuItem.separator())
|
|
|
|
// Add about & quit menu items
|
|
menu.addItem(NSMenuItem(title: "mi_preferences".localized, action: #selector(openPrefs), keyEquivalent: ","))
|
|
menu.addItem(NSMenuItem(title: "mi_about".localized, action: #selector(openAbout), keyEquivalent: ""))
|
|
menu.addItem(NSMenuItem(title: "mi_quit".localized, action: #selector(terminateApp), keyEquivalent: "q"))
|
|
|
|
// Make sure every item can be interacted with
|
|
menu.items.forEach({ (item) in
|
|
item.target = self
|
|
})
|
|
|
|
statusItem.menu = menu
|
|
statusItem.menu?.delegate = self
|
|
}
|
|
}
|
|
|
|
/**
|
|
Sets the status bar image based on a version string.
|
|
*/
|
|
func setStatusBarImage(version: String) {
|
|
setStatusBar(
|
|
image: MenuBarImageGenerator.textToImage(text: version)
|
|
)
|
|
}
|
|
|
|
/**
|
|
Sets the status bar image, based on the provided NSImage.
|
|
The image will be used as a template image.
|
|
*/
|
|
func setStatusBar(image: NSImage) {
|
|
if let button = statusItem.button {
|
|
image.isTemplate = true
|
|
button.image = image
|
|
}
|
|
}
|
|
|
|
// MARK: - Nicer callbacks
|
|
|
|
/**
|
|
Executes a specific callback and fires the completion callback,
|
|
while updating the UI as required. As long as the completion callback
|
|
does not fire, the app is presumed to be busy and the UI reflects this.
|
|
|
|
- Parameter execute: Callback of the work that needs to happen.
|
|
- Parameter completion: Callback that is fired when the work is done.
|
|
*/
|
|
private func waitAndExecute(_ execute: @escaping () -> Void, completion: @escaping () -> Void = {})
|
|
{
|
|
App.shared.busy = true
|
|
setBusyImage()
|
|
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
|
|
update()
|
|
execute()
|
|
App.shared.busy = false
|
|
|
|
DispatchQueue.main.async { [self] in
|
|
updatePhpVersionInStatusBar()
|
|
update()
|
|
completion()
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - User Interface
|
|
|
|
@objc func updatePhpVersionInStatusBar() {
|
|
App.shared.currentInstall = ActivePhpInstallation()
|
|
refreshIcon()
|
|
update()
|
|
}
|
|
|
|
func refreshIcon() {
|
|
DispatchQueue.main.async { [self] in
|
|
if (App.busy) {
|
|
setStatusBar(image: NSImage(named: NSImage.Name("StatusBarIcon"))!)
|
|
} else {
|
|
if Preferences.preferences[.shouldDisplayDynamicIcon] as! Bool == false {
|
|
// Static icon has been requested
|
|
setStatusBar(image: NSImage(named: NSImage.Name("StatusBarIconStatic"))!)
|
|
} else {
|
|
// The dynamic icon has been requested
|
|
let long = Preferences.preferences[.fullPhpVersionDynamicIcon] as! Bool
|
|
setStatusBarImage(version: long ? App.phpInstall!.version.long : App.phpInstall!.version.short)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@objc func reloadPhpMonitorMenu() {
|
|
waitAndExecute {
|
|
// This automatically reloads the menu
|
|
print("Reloading information about the PHP installation...")
|
|
} completion: {
|
|
// Add a slight delay to make sure it loads the new menu
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
|
// Open the menu again
|
|
MainMenu.shared.statusItem.button?.performClick(nil)
|
|
}
|
|
}
|
|
}
|
|
|
|
@objc func setBusyImage() {
|
|
DispatchQueue.main.async { [self] in
|
|
setStatusBar(image: NSImage(named: NSImage.Name("StatusBarIcon"))!)
|
|
}
|
|
}
|
|
|
|
// MARK: - Actions
|
|
|
|
@objc func restartPhpFpm() {
|
|
waitAndExecute {
|
|
Actions.restartPhpFpm()
|
|
}
|
|
}
|
|
|
|
@objc func restartAllServices() {
|
|
waitAndExecute {
|
|
Actions.restartDnsMasq()
|
|
Actions.restartPhpFpm()
|
|
Actions.restartNginx()
|
|
} completion: {
|
|
DispatchQueue.main.async {
|
|
LocalNotification.send(
|
|
title: "notification.services_restarted".localized,
|
|
subtitle: "notification.services_restarted_desc".localized
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
@objc func stopAllServices() {
|
|
waitAndExecute {
|
|
Actions.stopAllServices()
|
|
} completion: {
|
|
DispatchQueue.main.async {
|
|
LocalNotification.send(
|
|
title: "notification.services_stopped".localized,
|
|
subtitle: "notification.services_stopped_desc".localized
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
@objc func restartNginx() {
|
|
waitAndExecute {
|
|
Actions.restartNginx()
|
|
}
|
|
}
|
|
|
|
@objc func restartDnsMasq() {
|
|
waitAndExecute {
|
|
Actions.restartDnsMasq()
|
|
}
|
|
}
|
|
|
|
@objc func toggleExtension(sender: ExtensionMenuItem) {
|
|
waitAndExecute {
|
|
sender.phpExtension?.toggle()
|
|
|
|
if Preferences.preferences[.autoServiceRestartAfterExtensionToggle] as! Bool == true {
|
|
Actions.restartPhpFpm()
|
|
}
|
|
}
|
|
}
|
|
|
|
@objc func openPhpInfo() {
|
|
waitAndExecute {
|
|
// Write a file called `phpmon_phpinfo.php` to /tmp
|
|
try! "<?php phpinfo();".write(toFile: "/tmp/phpmon_phpinfo.php", atomically: true, encoding: .utf8)
|
|
|
|
// Tell php-cgi to run the PHP and output as an .html file
|
|
Shell.run("\(Paths.binPath)/php-cgi -q /tmp/phpmon_phpinfo.php > /tmp/phpmon_phpinfo.html")
|
|
} completion: {
|
|
// When this has been completed, open the URL to the file in the browser
|
|
NSWorkspace.shared.open(URL(string: "file:///private/tmp/phpmon_phpinfo.html")!)
|
|
}
|
|
}
|
|
|
|
@objc func forceRestartLatestPhp() {
|
|
// Tell the user the switch is about to occur
|
|
Alert.notify(message: "alert.force_reload.title".localized, info: "alert.force_reload.info".localized)
|
|
|
|
// Start switching
|
|
waitAndExecute {
|
|
Actions.fixMyPhp()
|
|
} completion: {
|
|
Alert.notify(message: "alert.force_reload_done.title".localized, info: "alert.force_reload_done.info".localized)
|
|
}
|
|
}
|
|
|
|
@objc func openActiveConfigFolder() {
|
|
if (App.phpInstall!.version.error) {
|
|
// php version was not identified
|
|
Actions.openGenericPhpConfigFolder()
|
|
return
|
|
}
|
|
|
|
// php version was identified
|
|
Actions.openPhpConfigFolder(version: App.phpInstall!.version.short)
|
|
}
|
|
|
|
@objc func openGlobalComposerFolder() {
|
|
Actions.openGlobalComposerFolder()
|
|
}
|
|
|
|
@objc func openValetConfigFolder() {
|
|
Actions.openValetConfigFolder()
|
|
}
|
|
|
|
@objc func switchToPhpVersion(sender: PhpMenuItem) {
|
|
setBusyImage()
|
|
App.shared.busy = true
|
|
|
|
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
|
|
// Update the PHP version in the status bar
|
|
updatePhpVersionInStatusBar()
|
|
|
|
// Update the menu
|
|
update()
|
|
|
|
let completion = {
|
|
// Mark as no longer busy
|
|
App.shared.busy = false
|
|
|
|
// Perform UI updates on main thread
|
|
DispatchQueue.main.async { [self] in
|
|
updatePhpVersionInStatusBar()
|
|
update()
|
|
|
|
// Send a notification that the switch has been completed
|
|
LocalNotification.send(
|
|
title: String(format: "notification.version_changed_title".localized, sender.version),
|
|
subtitle: String(format: "notification.version_changed_desc".localized, sender.version)
|
|
)
|
|
|
|
App.phpInstall?.notifyAboutBrokenPhpFpm()
|
|
}
|
|
}
|
|
|
|
// TODO: Allow for switcher to vary?
|
|
Actions.switchToPhpVersionUsingValet(
|
|
version: sender.version,
|
|
availableVersions: App.shared.availablePhpVersions,
|
|
completed: completion
|
|
)
|
|
}
|
|
}
|
|
|
|
@objc func openAbout() {
|
|
NSApplication.shared.activate(ignoringOtherApps: true)
|
|
NSApplication.shared.orderFrontStandardAboutPanel()
|
|
}
|
|
|
|
@objc func openPrefs() {
|
|
PrefsVC.show()
|
|
}
|
|
|
|
@objc func terminateApp() {
|
|
NSApplication.shared.terminate(nil)
|
|
}
|
|
|
|
// MARK: - Menu Delegate
|
|
|
|
func menuWillOpen(_ menu: NSMenu) {
|
|
// Make sure the shortcut key does not trigger this when the menu is open
|
|
App.shared.shortcutHotkey?.isPaused = true
|
|
}
|
|
|
|
func menuDidClose(_ menu: NSMenu) {
|
|
// When the menu is closed, allow the shortcut to work again
|
|
App.shared.shortcutHotkey?.isPaused = false
|
|
}
|
|
}
|