mirror of
https://github.com/nicoverbruggen/phpmon.git
synced 2025-08-07 20:10:08 +02:00
Compare commits
19 Commits
Author | SHA1 | Date | |
---|---|---|---|
0fc7f2c905 | |||
eebfa823f1 | |||
edc23f97de | |||
7b79cb7fe8 | |||
88d81f343e | |||
8486997f01 | |||
af55e7fabc | |||
2025c951e8 | |||
a71c6afc0d | |||
1bfcdd546b | |||
78702ae325 | |||
22c173c2c6 | |||
593ac60cae | |||
cea95f4b64 | |||
9427b206b4 | |||
ff6ca2b79d | |||
8de76dc95a | |||
5b319d2691 | |||
76390a687f |
47
README.md
47
README.md
@ -1,3 +1,48 @@
|
||||
# phpmon
|
||||
|
||||
phpmon is a macOS utility that runs on your Mac and displays the active PHP version in your status bar. Handy if you're running multiple versions of PHP with Homebrew and wish to see which version is currently linked.
|
||||
phpmon is a macOS utility that runs on your Mac and displays the active PHP version in your status bar.
|
||||
|
||||
<img src="./docs/screenshot.png" width="278px" alt="phpmon screenshot"/>
|
||||
|
||||
For me, it comes in handy when running multiple versions of PHP with Homebrew and you wish to be able to see at a glance which version is currently linked & active with Laravel Valet, and switch between versions.
|
||||
|
||||
This version of phpmon was developed for:
|
||||
|
||||
* macOS Mojave (10.14)
|
||||
* PHP 7.x with Homebrew 2.x (with support for PHP 5.6 and PHP 7.0 [as well](https://github.com/eXolnet/homebrew-deprecated))
|
||||
* Laravel Valet 2.2
|
||||
|
||||
## Why I built this
|
||||
|
||||
I wanted to be able to see at a glance which version of PHP was linked, and handle dealing with Laravel Valet in a simple app without having to deal with the terminal every time.
|
||||
|
||||
Initially, I had an Alfred workflow for this. But this does the job as well, while also showing me at all times which version of PHP is linked (which is the main benefit over e.g. an Alfred workflow).
|
||||
|
||||
## How it works
|
||||
|
||||
### Version detection
|
||||
|
||||
This utility runs `php -r 'print phpversion();'` in the background periodically (every 60 seconds) and extracts the version number.
|
||||
|
||||
### Switching PHP versions
|
||||
|
||||
This utility will detect which PHP versions you have installed via Homebrew, and then allows you to switch between them.
|
||||
|
||||
This means:
|
||||
|
||||
- You have at least the latest version of PHP installed (`php@7.3`)
|
||||
- You have installed Laravel Valet (`which valet` returns `/usr/local/bin/valet`)
|
||||
- You ran `valet trust`, which means Valet commands can be run without using sudo
|
||||
|
||||
The utility runs the following commands:
|
||||
|
||||
- Unlink all detected PHP versions
|
||||
- Switch to PHP 7.3 (this is done in order to ensure that Valet works, even when attempting to use PHP 5.6)
|
||||
- Tell Valet to switch to a specific PHP version
|
||||
- Link the desired version of PHP
|
||||
|
||||
### Want to know more?
|
||||
|
||||
If you want to know more about how this works, I recommend you check out the source code.
|
||||
|
||||
This app isn't very complicated after all. In the end, this just (conveniently) executes some shell commands.
|
||||
|
BIN
assets.sketch
Normal file
BIN
assets.sketch
Normal file
Binary file not shown.
BIN
docs/screenshot.png
Normal file
BIN
docs/screenshot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 314 KiB |
@ -8,31 +8,41 @@
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
C41C1B3722B0097F00E7CF16 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B3622B0097F00E7CF16 /* AppDelegate.swift */; };
|
||||
C41C1B3922B0097F00E7CF16 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B3822B0097F00E7CF16 /* ViewController.swift */; };
|
||||
C41C1B3922B0097F00E7CF16 /* LogViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B3822B0097F00E7CF16 /* LogViewController.swift */; };
|
||||
C41C1B3B22B0098000E7CF16 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C41C1B3A22B0098000E7CF16 /* Assets.xcassets */; };
|
||||
C41C1B3E22B0098000E7CF16 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C41C1B3C22B0098000E7CF16 /* Main.storyboard */; };
|
||||
C41C1B4722B009A400E7CF16 /* Shell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4622B009A400E7CF16 /* Shell.swift */; };
|
||||
C41C1B4922B00A9800E7CF16 /* ImageGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4822B00A9800E7CF16 /* ImageGenerator.swift */; };
|
||||
C41C1B4B22B019FF00E7CF16 /* PHPVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4A22B019FF00E7CF16 /* PHPVersion.swift */; };
|
||||
C41C1B4D22B0215A00E7CF16 /* Services.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4C22B0215A00E7CF16 /* Services.swift */; };
|
||||
C41C1B4922B00A9800E7CF16 /* MenuBarImageGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4822B00A9800E7CF16 /* MenuBarImageGenerator.swift */; };
|
||||
C41C1B4B22B019FF00E7CF16 /* PhpVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4A22B019FF00E7CF16 /* PhpVersion.swift */; };
|
||||
C41C1B4D22B0215A00E7CF16 /* Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4C22B0215A00E7CF16 /* Actions.swift */; };
|
||||
C476FF9822B0DD830098105B /* Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = C476FF9722B0DD830098105B /* Alert.swift */; };
|
||||
C4D8016622B1584700C6DA1B /* Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D8016522B1584700C6DA1B /* Environment.swift */; };
|
||||
C4811D2422D70A4700B5F6B3 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4811D2322D70A4700B5F6B3 /* App.swift */; };
|
||||
C4811D2A22D70F9A00B5F6B3 /* MainMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4811D2922D70F9A00B5F6B3 /* MainMenu.swift */; };
|
||||
C4D8016622B1584700C6DA1B /* Startup.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D8016522B1584700C6DA1B /* Startup.swift */; };
|
||||
C4EE188422D3386B00E126E5 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EE188322D3386B00E126E5 /* Constants.swift */; };
|
||||
C4F8C0A422D4F12C002EFE61 /* DateExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F8C0A322D4F12C002EFE61 /* DateExtension.swift */; };
|
||||
C4F8C0A622D4FA41002EFE61 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = C4F8C0A522D4FA41002EFE61 /* README.md */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
C41C1B3322B0097F00E7CF16 /* phpmon.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = phpmon.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
C41C1B3622B0097F00E7CF16 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
C41C1B3822B0097F00E7CF16 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
|
||||
C41C1B3822B0097F00E7CF16 /* LogViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogViewController.swift; sourceTree = "<group>"; };
|
||||
C41C1B3A22B0098000E7CF16 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
C41C1B3D22B0098000E7CF16 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||
C41C1B3F22B0098000E7CF16 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
C41C1B4022B0098000E7CF16 /* phpmon.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = phpmon.entitlements; sourceTree = "<group>"; };
|
||||
C41C1B4622B009A400E7CF16 /* Shell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shell.swift; sourceTree = "<group>"; };
|
||||
C41C1B4822B00A9800E7CF16 /* ImageGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageGenerator.swift; sourceTree = "<group>"; };
|
||||
C41C1B4A22B019FF00E7CF16 /* PHPVersion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PHPVersion.swift; sourceTree = "<group>"; };
|
||||
C41C1B4C22B0215A00E7CF16 /* Services.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Services.swift; sourceTree = "<group>"; };
|
||||
C41C1B4822B00A9800E7CF16 /* MenuBarImageGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuBarImageGenerator.swift; sourceTree = "<group>"; };
|
||||
C41C1B4A22B019FF00E7CF16 /* PhpVersion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpVersion.swift; sourceTree = "<group>"; };
|
||||
C41C1B4C22B0215A00E7CF16 /* Actions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Actions.swift; sourceTree = "<group>"; };
|
||||
C476FF9722B0DD830098105B /* Alert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Alert.swift; sourceTree = "<group>"; };
|
||||
C4D8016522B1584700C6DA1B /* Environment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Environment.swift; sourceTree = "<group>"; };
|
||||
C4811D2322D70A4700B5F6B3 /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = "<group>"; };
|
||||
C4811D2922D70F9A00B5F6B3 /* MainMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainMenu.swift; sourceTree = "<group>"; };
|
||||
C4D8016522B1584700C6DA1B /* Startup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Startup.swift; sourceTree = "<group>"; };
|
||||
C4EE188322D3386B00E126E5 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = "<group>"; };
|
||||
C4F8C0A322D4F12C002EFE61 /* DateExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateExtension.swift; sourceTree = "<group>"; };
|
||||
C4F8C0A522D4FA41002EFE61 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@ -49,6 +59,7 @@
|
||||
C41C1B2A22B0097F00E7CF16 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C4F8C0A522D4FA41002EFE61 /* README.md */,
|
||||
C41C1B3522B0097F00E7CF16 /* phpmon */,
|
||||
C41C1B3422B0097F00E7CF16 /* Products */,
|
||||
);
|
||||
@ -66,29 +77,73 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C41C1B3622B0097F00E7CF16 /* AppDelegate.swift */,
|
||||
C41C1B3822B0097F00E7CF16 /* ViewController.swift */,
|
||||
C41C1B3A22B0098000E7CF16 /* Assets.xcassets */,
|
||||
C41C1B3C22B0098000E7CF16 /* Main.storyboard */,
|
||||
C4EE188322D3386B00E126E5 /* Constants.swift */,
|
||||
C4811D2622D70CEF00B5F6B3 /* Singletons */,
|
||||
C41E181722CB61EB0072CF09 /* Classes */,
|
||||
C41E181822CB62200072CF09 /* View Controllers */,
|
||||
C4F8C0A222D4F100002EFE61 /* Extensions */,
|
||||
C41C1B3F22B0098000E7CF16 /* Info.plist */,
|
||||
C41C1B4022B0098000E7CF16 /* phpmon.entitlements */,
|
||||
C41C1B4E22B024F100E7CF16 /* Helpers */,
|
||||
C41C1B3A22B0098000E7CF16 /* Assets.xcassets */,
|
||||
);
|
||||
path = phpmon;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C41C1B4E22B024F100E7CF16 /* Helpers */ = {
|
||||
C41E181722CB61EB0072CF09 /* Classes */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C4811D2722D70D8E00B5F6B3 /* Commands */,
|
||||
C4811D2822D70D9C00B5F6B3 /* Helpers */,
|
||||
);
|
||||
path = Classes;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C41E181822CB62200072CF09 /* View Controllers */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C41C1B3C22B0098000E7CF16 /* Main.storyboard */,
|
||||
C41C1B3822B0097F00E7CF16 /* LogViewController.swift */,
|
||||
);
|
||||
path = "View Controllers";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C4811D2622D70CEF00B5F6B3 /* Singletons */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C41C1B4622B009A400E7CF16 /* Shell.swift */,
|
||||
C41C1B4822B00A9800E7CF16 /* ImageGenerator.swift */,
|
||||
C41C1B4A22B019FF00E7CF16 /* PHPVersion.swift */,
|
||||
C41C1B4C22B0215A00E7CF16 /* Services.swift */,
|
||||
C4811D2322D70A4700B5F6B3 /* App.swift */,
|
||||
C4811D2922D70F9A00B5F6B3 /* MainMenu.swift */,
|
||||
);
|
||||
path = Singletons;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C4811D2722D70D8E00B5F6B3 /* Commands */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C41C1B4C22B0215A00E7CF16 /* Actions.swift */,
|
||||
C4D8016522B1584700C6DA1B /* Startup.swift */,
|
||||
);
|
||||
path = Commands;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C4811D2822D70D9C00B5F6B3 /* Helpers */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C476FF9722B0DD830098105B /* Alert.swift */,
|
||||
C4D8016522B1584700C6DA1B /* Environment.swift */,
|
||||
C41C1B4A22B019FF00E7CF16 /* PhpVersion.swift */,
|
||||
C41C1B4822B00A9800E7CF16 /* MenuBarImageGenerator.swift */,
|
||||
);
|
||||
path = Helpers;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C4F8C0A222D4F100002EFE61 /* Extensions */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C4F8C0A322D4F12C002EFE61 /* DateExtension.swift */,
|
||||
);
|
||||
path = Extensions;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
@ -147,6 +202,7 @@
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
C4F8C0A622D4FA41002EFE61 /* README.md in Resources */,
|
||||
C41C1B3B22B0098000E7CF16 /* Assets.xcassets in Resources */,
|
||||
C41C1B3E22B0098000E7CF16 /* Main.storyboard in Resources */,
|
||||
);
|
||||
@ -159,14 +215,18 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
C4D8016622B1584700C6DA1B /* Environment.swift in Sources */,
|
||||
C4D8016622B1584700C6DA1B /* Startup.swift in Sources */,
|
||||
C4F8C0A422D4F12C002EFE61 /* DateExtension.swift in Sources */,
|
||||
C41C1B4722B009A400E7CF16 /* Shell.swift in Sources */,
|
||||
C41C1B4D22B0215A00E7CF16 /* Services.swift in Sources */,
|
||||
C41C1B4922B00A9800E7CF16 /* ImageGenerator.swift in Sources */,
|
||||
C41C1B3922B0097F00E7CF16 /* ViewController.swift in Sources */,
|
||||
C41C1B4D22B0215A00E7CF16 /* Actions.swift in Sources */,
|
||||
C4811D2422D70A4700B5F6B3 /* App.swift in Sources */,
|
||||
C41C1B4922B00A9800E7CF16 /* MenuBarImageGenerator.swift in Sources */,
|
||||
C4811D2A22D70F9A00B5F6B3 /* MainMenu.swift in Sources */,
|
||||
C41C1B3922B0097F00E7CF16 /* LogViewController.swift in Sources */,
|
||||
C41C1B3722B0097F00E7CF16 /* AppDelegate.swift in Sources */,
|
||||
C41C1B4B22B019FF00E7CF16 /* PHPVersion.swift in Sources */,
|
||||
C41C1B4B22B019FF00E7CF16 /* PhpVersion.swift in Sources */,
|
||||
C476FF9822B0DD830098105B /* Alert.swift in Sources */,
|
||||
C4EE188422D3386B00E126E5 /* Constants.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -11,121 +11,28 @@ import Cocoa
|
||||
@NSApplicationMain
|
||||
class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
|
||||
let statusItem = NSStatusBar.system.statusItem(
|
||||
withLength: 32
|
||||
)
|
||||
// MARK: - Variables
|
||||
let sharedShell : Shell
|
||||
let state : App
|
||||
let menu : MainMenu
|
||||
|
||||
var timer: Timer?
|
||||
var version: PHPVersion? = nil
|
||||
var availablePhpVersions : [String] = []
|
||||
var busy: Bool = false
|
||||
// MARK: - Initializer
|
||||
|
||||
override init() {
|
||||
self.sharedShell = Shell.user
|
||||
self.state = App.shared
|
||||
self.menu = MainMenu.shared
|
||||
super.init()
|
||||
}
|
||||
|
||||
// MARK: - Lifecycle
|
||||
|
||||
func applicationDidFinishLaunching(_ aNotification: Notification) {
|
||||
// Start with the icon
|
||||
self.setStatusBar(image: NSImage(named: NSImage.Name("StatusBarIcon"))!)
|
||||
// Perform environment boot checks
|
||||
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
|
||||
Environment.performBootChecks()
|
||||
self.availablePhpVersions = Services.detectPhpVersions()
|
||||
print("The following PHP versions were detected:")
|
||||
print(self.availablePhpVersions)
|
||||
self.updatePhpVersionInStatusBar()
|
||||
// Schedule a request to fetch the PHP version every 15 seconds
|
||||
Timer.scheduledTimer(
|
||||
timeInterval: 15,
|
||||
target: self,
|
||||
selector: #selector(self.updatePhpVersionInStatusBar),
|
||||
userInfo: nil,
|
||||
repeats: true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func setStatusBarImage(version: String) {
|
||||
self.setStatusBar(image: ImageGenerator.generateImageForStatusBar(width: 32.0, text: version))
|
||||
}
|
||||
|
||||
func setStatusBar(image: NSImage) {
|
||||
if let button = statusItem.button {
|
||||
image.isTemplate = true
|
||||
button.image = image
|
||||
}
|
||||
self.menu.startup()
|
||||
}
|
||||
|
||||
func applicationWillTerminate(_ aNotification: Notification) {
|
||||
// Insert code here to tear down your application
|
||||
}
|
||||
|
||||
@objc func updatePhpVersionInStatusBar() {
|
||||
self.version = PHPVersion()
|
||||
if (self.busy) {
|
||||
DispatchQueue.main.async {
|
||||
self.setStatusBar(image: NSImage(named: NSImage.Name("StatusBarIcon"))!)
|
||||
}
|
||||
} else {
|
||||
DispatchQueue.main.async {
|
||||
self.setStatusBarImage(version: self.version!.short)
|
||||
}
|
||||
}
|
||||
self.updateMenu()
|
||||
}
|
||||
|
||||
func updateMenu() {
|
||||
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
|
||||
let menu = NSMenu()
|
||||
var string = "We are not sure what version of PHP you are running."
|
||||
if (self.version != nil) {
|
||||
string = "You are running PHP \(self.version!.long)"
|
||||
}
|
||||
menu.addItem(NSMenuItem(title: string, action: nil, keyEquivalent: ""))
|
||||
menu.addItem(NSMenuItem.separator())
|
||||
if (self.availablePhpVersions.count > 0 && !self.busy) {
|
||||
for index in (0..<self.availablePhpVersions.count) {
|
||||
let version = self.availablePhpVersions[index]
|
||||
let action = #selector(self.switchToPhpVersion(sender:))
|
||||
let menuItem = NSMenuItem(title: "Switch to PHP \(version)", action: (version == self.version?.short) ? nil : action, keyEquivalent: "\(index + 1)")
|
||||
menuItem.tag = index
|
||||
menu.addItem(menuItem)
|
||||
}
|
||||
menu.addItem(NSMenuItem.separator())
|
||||
}
|
||||
if (self.busy) {
|
||||
menu.addItem(NSMenuItem(title: "Switching PHP versions...", action: nil, keyEquivalent: ""))
|
||||
menu.addItem(NSMenuItem.separator())
|
||||
}
|
||||
menu.addItem(NSMenuItem(title: "About phpmon", action: #selector(self.openAbout), keyEquivalent: ""))
|
||||
menu.addItem(NSMenuItem(title: "Quit phpmon", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q"))
|
||||
DispatchQueue.main.async {
|
||||
self.statusItem.menu = menu
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc public func openAbout() {
|
||||
NSApplication.shared.activate(ignoringOtherApps: true)
|
||||
NSApplication.shared.orderFrontStandardAboutPanel()
|
||||
}
|
||||
|
||||
@objc public func switchToPhpVersion(sender: AnyObject) {
|
||||
self.setStatusBar(image: NSImage(named: NSImage.Name("StatusBarIcon"))!)
|
||||
let index = sender.tag!
|
||||
let version = self.availablePhpVersions[index]
|
||||
self.busy = true
|
||||
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
|
||||
// Update the PHP version in the status bar
|
||||
self.updatePhpVersionInStatusBar()
|
||||
// Update the menu
|
||||
self.updateMenu()
|
||||
// Switch the PHP version
|
||||
Services.switchToPhpVersion(version: version, availableVersions: self.availablePhpVersions)
|
||||
// Mark as no longer busy
|
||||
self.busy = false
|
||||
// Perform UI updates on main thread
|
||||
DispatchQueue.main.async {
|
||||
self.updatePhpVersionInStatusBar()
|
||||
self.updateMenu()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,71 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Application-->
|
||||
<scene sceneID="JPo-4y-FX3">
|
||||
<objects>
|
||||
<application id="hnw-xV-0zn" sceneMemberID="viewController">
|
||||
<menu key="mainMenu" title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
|
||||
<items>
|
||||
<menuItem title="phpmon" id="1Xt-HY-uBw">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="phpmon" systemMenu="apple" id="uQy-DD-JDr">
|
||||
<items>
|
||||
<menuItem title="About phpmon" id="5kV-Vb-QxS">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="orderFrontStandardAboutPanel:" target="Ady-hI-5gd" id="Exp-CZ-Vem"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="VOq-y0-SEH"/>
|
||||
<menuItem title="Quit phpmon" keyEquivalent="q" id="4sb-4s-VLi">
|
||||
<connections>
|
||||
<action selector="terminate:" target="Ady-hI-5gd" id="Te7-pn-YzF"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Help" id="wpr-3q-Mcd">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Help" systemMenu="help" id="F2S-fz-NVQ">
|
||||
<items>
|
||||
<menuItem title="phpmon Help" keyEquivalent="?" id="FKE-Sm-Kum">
|
||||
<connections>
|
||||
<action selector="showHelp:" target="Ady-hI-5gd" id="y7X-2Q-9no"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
<connections>
|
||||
<outlet property="delegate" destination="Voe-Tx-rLC" id="PrD-fu-P6m"/>
|
||||
</connections>
|
||||
</application>
|
||||
<customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="phpmon" customModuleProvider="target"/>
|
||||
<customObject id="YLy-65-1bz" customClass="NSFontManager"/>
|
||||
<customObject id="Ady-hI-5gd" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="75" y="0.0"/>
|
||||
</scene>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="hIz-AP-VOD">
|
||||
<objects>
|
||||
<viewController id="XfG-lQ-9wD" customClass="ViewController" customModule="phpmon" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" identifier="main" id="m2S-Jp-Qdl">
|
||||
<rect key="frame" x="0.0" y="0.0" width="425" height="264"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<customObject id="rPt-NT-nkU" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="74.5" y="217"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
@ -7,26 +7,12 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import AppKit
|
||||
|
||||
class Services {
|
||||
public static func mysqlIsRunning() -> Bool {
|
||||
let running = Shell.execute(command: "launchctl list | grep homebrew.mxcl.mysql")
|
||||
if (running != "") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
public static func nginxIsRunning() -> Bool {
|
||||
let running = Shell.execute(command: "launchctl list | grep homebrew.mxcl.nginx")
|
||||
if (running != "") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
class Actions {
|
||||
|
||||
public static func detectPhpVersions() -> [String] {
|
||||
let files = Shell.execute(command: "ls /usr/local/opt | grep php@")
|
||||
let files = Shell.user.pipe("ls /usr/local/opt | grep php@")
|
||||
var versions = files.components(separatedBy: "\n")
|
||||
// Remove all empty strings
|
||||
versions.removeAll { (string) -> Bool in
|
||||
@ -42,11 +28,20 @@ class Services {
|
||||
|
||||
public static func switchToPhpVersion(version: String, availableVersions: [String]) {
|
||||
availableVersions.forEach { (version) in
|
||||
_ = Shell.execute(command: "brew unlink php@\(version)")
|
||||
Shell.user.run("brew unlink php@\(version)")
|
||||
}
|
||||
if (availableVersions.contains("7.3")) {
|
||||
_ = Shell.execute(command: "brew link php@7.3")
|
||||
_ = Shell.execute(command: "valet use php@\(version)")
|
||||
Shell.user.run("brew link php@7.3")
|
||||
if (version == Constants.LatestPhpVersion) {
|
||||
Shell.user.run( "valet use php")
|
||||
} else {
|
||||
Shell.user.run("valet use php@\(version)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static func openPhpConfigFolder(version: String) {
|
||||
let files = [NSURL(fileURLWithPath: "/usr/local/etc/php/\(version)/php.ini")];
|
||||
NSWorkspace.shared.activateFileViewerSelecting(files as [URL]);
|
||||
}
|
||||
}
|
61
phpmon/Classes/Commands/Startup.swift
Normal file
61
phpmon/Classes/Commands/Startup.swift
Normal file
@ -0,0 +1,61 @@
|
||||
//
|
||||
// Environment.swift
|
||||
// phpmon
|
||||
//
|
||||
// Created by Nico Verbruggen on 12/06/2019.
|
||||
// Copyright © 2019 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class Startup {
|
||||
|
||||
public static func checkEnvironment()
|
||||
{
|
||||
self.presentAlertOnMainThreadIf(
|
||||
!Shell.user.pipe("which php").contains("/usr/local/bin/php"),
|
||||
messageText: "PHP is not correctly installed",
|
||||
informativeText: "You must install PHP via brew. Try running `which php` in Terminal, it should return `/usr/local/bin/php`. The app will not work correctly until you resolve this issue."
|
||||
)
|
||||
|
||||
self.presentAlertOnMainThreadIf(
|
||||
!Shell.user.pipe("ls /usr/local/opt | grep php@7.3").contains("php@7.3"),
|
||||
messageText: "PHP 7.3 is not correctly installed",
|
||||
informativeText: "PHP 7.3 alias was not found in `/usr/local/opt`. The app will not work correctly until you resolve this issue."
|
||||
)
|
||||
|
||||
self.presentAlertOnMainThreadIf(
|
||||
!Shell.user.pipe("which valet").contains("/usr/local/bin/valet"),
|
||||
messageText: "Laravel Valet is not correctly installed",
|
||||
informativeText: "You must install Valet via brew. Try running `which valet` in Terminal, it should return `/usr/local/bin/valet`. The app will not work correctly until you resolve this issue."
|
||||
)
|
||||
|
||||
self.presentAlertOnMainThreadIf(
|
||||
!Shell.user.pipe("cat /private/etc/sudoers.d/brew").contains("/usr/local/bin/brew"),
|
||||
messageText: "Brew has not been added to sudoers.d",
|
||||
informativeText: "You must run `sudo valet trust` to ensure Valet can start and stop services without having to use sudo every time. The app will not work correctly until you resolve this issue."
|
||||
)
|
||||
|
||||
self.presentAlertOnMainThreadIf(
|
||||
!Shell.user.pipe("cat /private/etc/sudoers.d/valet").contains("/usr/local/bin/valet"),
|
||||
messageText: "Valet has not been added to sudoers.d",
|
||||
informativeText: "You must run `sudo valet trust` to ensure Valet can start and stop services without having to use sudo every time. The app will not work correctly until you resolve this issue."
|
||||
)
|
||||
}
|
||||
|
||||
private static func presentAlertOnMainThreadIf(
|
||||
_ condition: Bool,
|
||||
messageText: String,
|
||||
informativeText: String
|
||||
)
|
||||
{
|
||||
if (condition) {
|
||||
DispatchQueue.main.async {
|
||||
Alert.present(
|
||||
messageText: messageText,
|
||||
informativeText: informativeText
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -8,14 +8,15 @@
|
||||
|
||||
import Cocoa
|
||||
|
||||
class ImageGenerator {
|
||||
class MenuBarImageGenerator {
|
||||
|
||||
public static func generateImageForStatusBar(width: CGFloat = 30.0, height: CGFloat = 20.0, text: String) -> NSImage {
|
||||
let image = NSImage(size: NSMakeSize(width, height))
|
||||
let font = NSFont.systemFont(ofSize: 14)
|
||||
/**
|
||||
Takes a string and converts it to an image that can be displayed in the menu bar.
|
||||
The width of the NSImage depends on the length of the text.
|
||||
*/
|
||||
public static func textToImage(text: String) -> NSImage {
|
||||
|
||||
let imageRect = CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height)
|
||||
let textRect = CGRect(x: 5, y: -1, width: image.size.width, height: image.size.height)
|
||||
let font = NSFont.systemFont(ofSize: 14, weight: .medium)
|
||||
|
||||
let textStyle = NSMutableParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle
|
||||
let textFontAttributes = [
|
||||
@ -24,8 +25,23 @@ class ImageGenerator {
|
||||
NSAttributedString.Key.paragraphStyle: textStyle
|
||||
]
|
||||
|
||||
let targetImage: NSImage = NSImage(size: image.size)
|
||||
let padding : CGFloat = 2.0;
|
||||
|
||||
// Create an attributed string so we'll know how wide the item will need to be
|
||||
let attributedString = NSAttributedString(string: text, attributes: textFontAttributes)
|
||||
let textSize = attributedString.size()
|
||||
|
||||
// Add padding to the width of the menu bar item
|
||||
let size = NSSize(width: textSize.width + (2 * padding), height: textSize.height)
|
||||
let image = NSImage(size: size)
|
||||
|
||||
// Set the image rect with the appropriate dimensions
|
||||
let imageRect = CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height)
|
||||
|
||||
// Position the text inside the image rect
|
||||
let textRect = CGRect(x: padding, y: 0, width: image.size.width, height: image.size.height)
|
||||
|
||||
let targetImage: NSImage = NSImage(size: image.size)
|
||||
let rep: NSBitmapImageRep = NSBitmapImageRep(
|
||||
bitmapDataPlanes: nil,
|
||||
pixelsWide: Int(image.size.width),
|
@ -8,21 +8,21 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
class PHPVersion {
|
||||
class PhpVersion {
|
||||
|
||||
var short : String = "???"
|
||||
var long : String = "???"
|
||||
|
||||
init() {
|
||||
// Get the info about the PHP installation
|
||||
let output = Shell.execute(command: "php -v")
|
||||
// Get everything before "(cli)" (PHP X.X.X (cli) ...)
|
||||
var version = output.components(separatedBy: " (cli)")[0]
|
||||
// Strip away the text before the version number
|
||||
version = version.components(separatedBy: "PHP ")[1]
|
||||
let version = Shell.user
|
||||
// Get the version directly from PHP
|
||||
.pipe("php -r 'print phpversion();'")
|
||||
|
||||
// That's the long version
|
||||
self.long = version
|
||||
|
||||
// Next up, let's strip away the minor version number
|
||||
let segments = version.components(separatedBy: ".")
|
||||
let segments = long.components(separatedBy: ".")
|
||||
// Get the first two elements
|
||||
self.short = segments[0...1].joined(separator: ".")
|
||||
}
|
26
phpmon/Constants.swift
Normal file
26
phpmon/Constants.swift
Normal file
@ -0,0 +1,26 @@
|
||||
//
|
||||
// Constants.swift
|
||||
// phpmon
|
||||
//
|
||||
// Created by Nico Verbruggen on 08/07/2019.
|
||||
// Copyright © 2019 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
|
||||
class Constants {
|
||||
|
||||
/**
|
||||
* The PHP versions supported by this application.
|
||||
*/
|
||||
static let SupportedPhpVersions = [
|
||||
"5.6", "7.0", "7.1", "7.2", "7.3"
|
||||
]
|
||||
|
||||
/**
|
||||
Which php version is aliased as `php` to brew?
|
||||
This is usually the latest PHP version.
|
||||
*/
|
||||
static let LatestPhpVersion = "7.3"
|
||||
|
||||
}
|
19
phpmon/Extensions/DateExtension.swift
Normal file
19
phpmon/Extensions/DateExtension.swift
Normal file
@ -0,0 +1,19 @@
|
||||
//
|
||||
// Date.swift
|
||||
// phpmon
|
||||
//
|
||||
// Created by Nico Verbruggen on 09/07/2019.
|
||||
// Copyright © 2019 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
|
||||
extension Date
|
||||
{
|
||||
func toString() -> String
|
||||
{
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
|
||||
return dateFormatter.string(from: self)
|
||||
}
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
//
|
||||
// Environment.swift
|
||||
// phpmon
|
||||
//
|
||||
// Created by Nico Verbruggen on 12/06/2019.
|
||||
// Copyright © 2019 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class Environment {
|
||||
public static func performBootChecks()
|
||||
{
|
||||
if (!Shell.execute(command: "which php").contains("/usr/local/bin/php")) {
|
||||
DispatchQueue.main.async {
|
||||
Alert.present(
|
||||
messageText: "PHP is not correctly installed",
|
||||
informativeText: "You must install PHP via brew. Try running `which php` in Terminal, it should return `/usr/local/bin/php`. The app will not work correctly until you resolve this issue."
|
||||
)
|
||||
}
|
||||
}
|
||||
if (!Shell.execute(command: "ls /usr/local/opt | grep php@7.3").contains("php@7.3")) {
|
||||
DispatchQueue.main.async {
|
||||
Alert.present(
|
||||
messageText: "PHP 7.3 is not correctly installed",
|
||||
informativeText: "PHP 7.3 alias was not found in `/usr/local/opt`. The app will not work correctly until you resolve this issue."
|
||||
)
|
||||
}
|
||||
}
|
||||
if (!Shell.execute(command: "which valet").contains("/usr/local/bin/valet")) {
|
||||
DispatchQueue.main.async {
|
||||
Alert.present(
|
||||
messageText: "Laravel Valet is not correctly installed",
|
||||
informativeText: "You must install Valet via brew. Try running `which valet` in Terminal, it should return `/usr/local/bin/valet`. The app will not work correctly until you resolve this issue."
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
//
|
||||
// Shell.swift
|
||||
// phpmon
|
||||
//
|
||||
// Created by Nico Verbruggen on 11/06/2019.
|
||||
// Copyright © 2019 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
|
||||
class Shell {
|
||||
public static func execute(command: String) -> String
|
||||
{
|
||||
let task = Process()
|
||||
task.launchPath = "/bin/bash"
|
||||
task.arguments = ["--login", "-c", command]
|
||||
|
||||
let pipe = Pipe()
|
||||
task.standardOutput = pipe
|
||||
task.launch()
|
||||
|
||||
let data = pipe.fileHandleForReading.readDataToEndOfFile()
|
||||
let output: String = NSString(data: data, encoding: String.Encoding.utf8.rawValue)! as String
|
||||
|
||||
return output
|
||||
}
|
||||
}
|
@ -17,9 +17,11 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<string>1.3</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>4</string>
|
||||
<string>10</string>
|
||||
<key>LSApplicationCategoryType</key>
|
||||
<string>public.app-category.utilities</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
|
||||
<key>LSUIElement</key>
|
||||
|
40
phpmon/Singletons/App.swift
Normal file
40
phpmon/Singletons/App.swift
Normal file
@ -0,0 +1,40 @@
|
||||
//
|
||||
// StateManager.swift
|
||||
// phpmon
|
||||
//
|
||||
// Created by Nico Verbruggen on 11/07/2019.
|
||||
// Copyright © 2019 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
|
||||
class App {
|
||||
|
||||
static let shared = App()
|
||||
|
||||
/**
|
||||
Whether the application is busy switching versions.
|
||||
*/
|
||||
var busy: Bool = false
|
||||
|
||||
/**
|
||||
The currently active version of PHP.
|
||||
*/
|
||||
var currentVersion: PhpVersion? = nil
|
||||
|
||||
/**
|
||||
All available versions of PHP.
|
||||
*/
|
||||
var availablePhpVersions : [String] = []
|
||||
|
||||
/**
|
||||
The timer that will periodically fetch the PHP version that is currently active.
|
||||
*/
|
||||
var timer: Timer?
|
||||
|
||||
/**
|
||||
The window controller that will show the log.
|
||||
*/
|
||||
var windowController: NSWindowController? = nil
|
||||
|
||||
}
|
154
phpmon/Singletons/MainMenu.swift
Normal file
154
phpmon/Singletons/MainMenu.swift
Normal file
@ -0,0 +1,154 @@
|
||||
//
|
||||
// MainMenu.swift
|
||||
// phpmon
|
||||
//
|
||||
// Created by Nico Verbruggen on 11/07/2019.
|
||||
// Copyright © 2019 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
|
||||
class MainMenu: NSObject, NSWindowDelegate {
|
||||
|
||||
static let shared = MainMenu()
|
||||
|
||||
let statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
|
||||
|
||||
// MARK: - UI related
|
||||
|
||||
public func startup() {
|
||||
// Start with the icon
|
||||
self.setStatusBar(image: NSImage(named: NSImage.Name("StatusBarIcon"))!)
|
||||
// Perform environment boot checks
|
||||
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
|
||||
Startup.checkEnvironment()
|
||||
App.shared.availablePhpVersions = Actions.detectPhpVersions()
|
||||
self.updatePhpVersionInStatusBar()
|
||||
// Schedule a request to fetch the PHP version every 60 seconds
|
||||
DispatchQueue.main.async {
|
||||
App.shared.timer = Timer.scheduledTimer(
|
||||
timeInterval: 60,
|
||||
target: self,
|
||||
selector: #selector(self.updatePhpVersionInStatusBar),
|
||||
userInfo: nil,
|
||||
repeats: true
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func update() {
|
||||
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
|
||||
let menu = NSMenu()
|
||||
var string = "We are not sure what version of PHP you are running."
|
||||
if (App.shared.currentVersion != nil) {
|
||||
string = "You are running PHP \(App.shared.currentVersion!.long)"
|
||||
}
|
||||
menu.addItem(NSMenuItem(title: string, action: nil, keyEquivalent: ""))
|
||||
menu.addItem(NSMenuItem.separator())
|
||||
if (App.shared.availablePhpVersions.count > 0 && !App.shared.busy) {
|
||||
var shortcutKey = 1
|
||||
for index in (0..<App.shared.availablePhpVersions.count).reversed() {
|
||||
let version = App.shared.availablePhpVersions[index]
|
||||
let action = #selector(self.switchToPhpVersion(sender:))
|
||||
let menuItem = NSMenuItem(title: "Switch to PHP \(version)", action: (version == App.shared.currentVersion?.short) ? nil : action, keyEquivalent: "\(shortcutKey)")
|
||||
menuItem.tag = index
|
||||
shortcutKey = shortcutKey + 1
|
||||
menu.addItem(menuItem)
|
||||
}
|
||||
menu.addItem(NSMenuItem.separator())
|
||||
}
|
||||
if (App.shared.busy) {
|
||||
menu.addItem(NSMenuItem(title: "Switching PHP versions...", action: nil, keyEquivalent: ""))
|
||||
menu.addItem(NSMenuItem.separator())
|
||||
}
|
||||
if (App.shared.currentVersion != nil) {
|
||||
menu.addItem(NSMenuItem(title: "PHP configuration file (php.ini)", action: #selector(self.openActiveConfigFolder), keyEquivalent: ""))
|
||||
}
|
||||
menu.addItem(NSMenuItem(title: "View shell output", action: #selector(self.openOutput), keyEquivalent: ""))
|
||||
menu.addItem(NSMenuItem.separator())
|
||||
menu.addItem(NSMenuItem(title: "About phpmon", action: #selector(self.openAbout), keyEquivalent: ""))
|
||||
menu.items.forEach({ (item) in
|
||||
item.target = self
|
||||
})
|
||||
menu.addItem(NSMenuItem(title: "Quit phpmon", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q"))
|
||||
DispatchQueue.main.async {
|
||||
self.statusItem.menu = menu
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func setStatusBarImage(version: String) {
|
||||
self.setStatusBar(
|
||||
image: MenuBarImageGenerator.textToImage(
|
||||
text: version
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
func setStatusBar(image: NSImage) {
|
||||
if let button = statusItem.button {
|
||||
image.isTemplate = true
|
||||
button.image = image
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Callable via Obj-C (#selector)
|
||||
|
||||
@objc func openOutput() {
|
||||
LogViewController.show(delegate: self)
|
||||
}
|
||||
|
||||
@objc func updatePhpVersionInStatusBar() {
|
||||
App.shared.currentVersion = PhpVersion()
|
||||
if (App.shared.busy) {
|
||||
DispatchQueue.main.async {
|
||||
self.setStatusBar(image: NSImage(named: NSImage.Name("StatusBarIcon"))!)
|
||||
}
|
||||
} else {
|
||||
DispatchQueue.main.async {
|
||||
self.setStatusBarImage(version: App.shared.currentVersion!.short)
|
||||
}
|
||||
}
|
||||
self.update()
|
||||
}
|
||||
|
||||
@objc public func openAbout() {
|
||||
NSApplication.shared.activate(ignoringOtherApps: true)
|
||||
NSApplication.shared.orderFrontStandardAboutPanel()
|
||||
}
|
||||
|
||||
@objc public func openActiveConfigFolder() {
|
||||
Actions.openPhpConfigFolder(version: App.shared.currentVersion!.short)
|
||||
}
|
||||
|
||||
@objc public func switchToPhpVersion(sender: AnyObject) {
|
||||
self.setStatusBar(image: NSImage(named: NSImage.Name("StatusBarIcon"))!)
|
||||
let index = sender.tag!
|
||||
let version = App.shared.availablePhpVersions[index]
|
||||
App.shared.busy = true
|
||||
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
|
||||
// Update the PHP version in the status bar
|
||||
self.updatePhpVersionInStatusBar()
|
||||
// Update the menu
|
||||
self.update()
|
||||
// Switch the PHP version
|
||||
Actions.switchToPhpVersion(
|
||||
version: version,
|
||||
availableVersions: App.shared.availablePhpVersions
|
||||
)
|
||||
// Mark as no longer busy
|
||||
App.shared.busy = false
|
||||
// Perform UI updates on main thread
|
||||
DispatchQueue.main.async {
|
||||
self.updatePhpVersionInStatusBar()
|
||||
self.update()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func windowWillClose(_ notification: Notification) {
|
||||
App.shared.windowController = nil
|
||||
Shell.user.delegate = nil
|
||||
}
|
||||
}
|
70
phpmon/Singletons/Shell.swift
Normal file
70
phpmon/Singletons/Shell.swift
Normal file
@ -0,0 +1,70 @@
|
||||
//
|
||||
// Shell.swift
|
||||
// phpmon
|
||||
//
|
||||
// Created by Nico Verbruggen on 11/06/2019.
|
||||
// Copyright © 2019 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
|
||||
protocol ShellDelegate: class {
|
||||
func didCompleteCommand(historyItem: ShellHistoryItem)
|
||||
}
|
||||
|
||||
class ShellHistoryItem {
|
||||
var command: String
|
||||
var output: String
|
||||
var date: Date
|
||||
|
||||
init(command: String, output: String) {
|
||||
self.command = command
|
||||
self.output = output
|
||||
self.date = Date()
|
||||
}
|
||||
}
|
||||
|
||||
class Shell {
|
||||
|
||||
// Singleton to access a user shell (with --login)
|
||||
static let user = Shell()
|
||||
|
||||
var history : [ShellHistoryItem] = []
|
||||
|
||||
var delegate : ShellDelegate?
|
||||
|
||||
/// Runs a shell command without using the description.
|
||||
public func run(_ command: String) {
|
||||
// Equivalent of piping to /dev/null; don't do anything with the string
|
||||
_ = self.pipe(command)
|
||||
}
|
||||
|
||||
/// Runs a shell command and returns the output.
|
||||
public func pipe(_ command: String) -> String {
|
||||
let task = Process()
|
||||
task.launchPath = "/bin/bash"
|
||||
task.arguments = ["--login", "-c", command]
|
||||
|
||||
let pipe = Pipe()
|
||||
task.standardOutput = pipe
|
||||
task.launch()
|
||||
|
||||
let data = pipe.fileHandleForReading.readDataToEndOfFile()
|
||||
|
||||
let output: String = NSString(
|
||||
data: data,
|
||||
encoding: String.Encoding.utf8.rawValue
|
||||
)!.replacingOccurrences(
|
||||
of: "\u{1B}(B\u{1B}[m",
|
||||
with: ""
|
||||
) as String
|
||||
|
||||
let historyItem = ShellHistoryItem(command: command, output: output)
|
||||
history.append(historyItem)
|
||||
// Keep the last 100 items
|
||||
history = history.suffix(100)
|
||||
delegate?.didCompleteCommand(historyItem: historyItem)
|
||||
|
||||
return output
|
||||
}
|
||||
}
|
120
phpmon/View Controllers/Base.lproj/Main.storyboard
Normal file
120
phpmon/View Controllers/Base.lproj/Main.storyboard
Normal file
@ -0,0 +1,120 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Application-->
|
||||
<scene sceneID="JPo-4y-FX3">
|
||||
<objects>
|
||||
<application id="hnw-xV-0zn" sceneMemberID="viewController">
|
||||
<menu key="mainMenu" title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
|
||||
<items>
|
||||
<menuItem title="phpmon" id="1Xt-HY-uBw">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="phpmon" systemMenu="apple" id="uQy-DD-JDr">
|
||||
<items>
|
||||
<menuItem title="About phpmon" id="5kV-Vb-QxS">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="orderFrontStandardAboutPanel:" target="Ady-hI-5gd" id="Exp-CZ-Vem"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="VOq-y0-SEH"/>
|
||||
<menuItem title="Quit phpmon" keyEquivalent="q" id="4sb-4s-VLi">
|
||||
<connections>
|
||||
<action selector="terminate:" target="Ady-hI-5gd" id="Te7-pn-YzF"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Help" id="wpr-3q-Mcd">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Help" systemMenu="help" id="F2S-fz-NVQ">
|
||||
<items>
|
||||
<menuItem title="phpmon Help" keyEquivalent="?" id="FKE-Sm-Kum">
|
||||
<connections>
|
||||
<action selector="showHelp:" target="Ady-hI-5gd" id="y7X-2Q-9no"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
<connections>
|
||||
<outlet property="delegate" destination="Voe-Tx-rLC" id="PrD-fu-P6m"/>
|
||||
</connections>
|
||||
</application>
|
||||
<customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="phpmon" customModuleProvider="target"/>
|
||||
<customObject id="YLy-65-1bz" customClass="NSFontManager"/>
|
||||
<customObject id="Ady-hI-5gd" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="134" y="40"/>
|
||||
</scene>
|
||||
<!--Log View Controller-->
|
||||
<scene sceneID="hIz-AP-VOD">
|
||||
<objects>
|
||||
<viewController storyboardIdentifier="logWindow" id="XfG-lQ-9wD" customClass="LogViewController" customModule="phpmon" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" identifier="main" id="m2S-Jp-Qdl">
|
||||
<rect key="frame" x="0.0" y="0.0" width="662" height="475"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ICa-gx-jgq">
|
||||
<rect key="frame" x="573" y="8" width="75" height="32"/>
|
||||
<buttonCell key="cell" type="push" title="Close" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="3md-FI-EWa">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="pressed:" target="XfG-lQ-9wD" id="fIC-Bz-vTK"/>
|
||||
</connections>
|
||||
</button>
|
||||
<scrollView borderType="line" horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" translatesAutoresizingMaskIntoConstraints="NO" id="vZy-5S-021">
|
||||
<rect key="frame" x="20" y="46" width="622" height="409"/>
|
||||
<clipView key="contentView" drawsBackground="NO" copiesOnScroll="NO" id="s5L-AU-0fw">
|
||||
<rect key="frame" x="1" y="1" width="620" height="407"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textView importsGraphics="NO" richText="NO" verticallyResizable="YES" smartInsertDelete="YES" id="tN6-Y9-1pA">
|
||||
<rect key="frame" x="0.0" y="0.0" width="620" height="407"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
<size key="minSize" width="620" height="407"/>
|
||||
<size key="maxSize" width="620" height="10000000"/>
|
||||
<color key="insertionPointColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
||||
</textView>
|
||||
</subviews>
|
||||
</clipView>
|
||||
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="Kho-JF-NZJ">
|
||||
<rect key="frame" x="-100" y="-100" width="240" height="16"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
<scroller key="verticalScroller" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="qp7-7R-gTO">
|
||||
<rect key="frame" x="605" y="1" width="16" height="407"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
</scrollView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="vZy-5S-021" firstAttribute="leading" secondItem="m2S-Jp-Qdl" secondAttribute="leading" constant="20" id="K0k-oE-r37"/>
|
||||
<constraint firstAttribute="trailing" secondItem="ICa-gx-jgq" secondAttribute="trailing" constant="20" id="LFS-0E-Ibw"/>
|
||||
<constraint firstItem="vZy-5S-021" firstAttribute="top" secondItem="m2S-Jp-Qdl" secondAttribute="top" constant="20" id="Nec-oI-CjE"/>
|
||||
<constraint firstAttribute="trailing" secondItem="vZy-5S-021" secondAttribute="trailing" constant="20" id="kBJ-O5-eYI"/>
|
||||
<constraint firstAttribute="bottom" secondItem="ICa-gx-jgq" secondAttribute="bottom" constant="15" id="kYB-Fn-DSA"/>
|
||||
<constraint firstItem="ICa-gx-jgq" firstAttribute="top" secondItem="vZy-5S-021" secondAttribute="bottom" constant="10" id="xdn-yU-LVb"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="textView" destination="tN6-Y9-1pA" id="z77-me-Od6"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<customObject id="rPt-NT-nkU" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-105" y="377.5"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
72
phpmon/View Controllers/LogViewController.swift
Normal file
72
phpmon/View Controllers/LogViewController.swift
Normal file
@ -0,0 +1,72 @@
|
||||
//
|
||||
// ViewController.swift
|
||||
// phpmon
|
||||
//
|
||||
// Created by Nico Verbruggen on 11/06/2019.
|
||||
// Copyright © 2019 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
|
||||
class LogViewController: NSViewController, ShellDelegate {
|
||||
|
||||
public static func show(delegate: NSWindowDelegate? = nil) {
|
||||
if (App.shared.windowController == nil) {
|
||||
let vc = NSStoryboard(name: "Main", bundle: nil).instantiateController(withIdentifier: "logWindow") as! LogViewController
|
||||
Shell.user.delegate = vc
|
||||
let window = NSWindow(contentViewController: vc)
|
||||
window.title = "Terminal Output"
|
||||
window.delegate = delegate
|
||||
App.shared.windowController = NSWindowController(window: window)
|
||||
}
|
||||
App.shared.windowController!.showWindow(self)
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
}
|
||||
|
||||
@IBOutlet var textView: NSTextView!
|
||||
|
||||
public func appendHistoryItem(_ historyItem: ShellHistoryItem) {
|
||||
self.append(
|
||||
"""
|
||||
======
|
||||
@ \(historyItem.date.toString())
|
||||
------
|
||||
$ \(historyItem.command)
|
||||
------
|
||||
> \(historyItem.output)
|
||||
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
public func append(_ text : String) {
|
||||
self.textView.textStorage?.append(
|
||||
NSAttributedString(
|
||||
string: text,
|
||||
attributes: [
|
||||
NSAttributedString.Key.font: NSFont(name: "Menlo", size: 12.0)!
|
||||
]
|
||||
)
|
||||
)
|
||||
self.textView.scrollToEndOfDocument(nil)
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
self.textView.isEditable = false
|
||||
for entry in Shell.user.history {
|
||||
self.appendHistoryItem(entry)
|
||||
}
|
||||
}
|
||||
|
||||
func didCompleteCommand(historyItem: ShellHistoryItem) {
|
||||
self.appendHistoryItem(historyItem)
|
||||
}
|
||||
|
||||
@IBAction func pressed(_ sender: Any) {
|
||||
self.view.window?.windowController?.close()
|
||||
}
|
||||
|
||||
deinit {
|
||||
print("VC deallocated")
|
||||
}
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
//
|
||||
// ViewController.swift
|
||||
// phpmon
|
||||
//
|
||||
// Created by Nico Verbruggen on 11/06/2019.
|
||||
// Copyright © 2019 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
|
||||
class ViewController: NSViewController {
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
// Do any additional setup after loading the view.
|
||||
}
|
||||
|
||||
override var representedObject: Any? {
|
||||
didSet {
|
||||
// Update the view, if already loaded.
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
Reference in New Issue
Block a user