1
0
mirror of https://github.com/nicoverbruggen/phpmon.git synced 2025-08-08 20:33:01 +02:00

Compare commits

...

23 Commits
v3.4 ... v4.0

Author SHA1 Message Date
015f406ddf 📝 Update SECURITY.md 2021-11-28 15:38:07 +01:00
e1a97672b5 🔧 Bump build, reorganize files 2021-11-28 15:11:00 +01:00
493b5945f9 👌 Major changes to PHP version detection
* The information extracted from Homebrew's JSON command now also
  includes information about linked keg and installations.

* The mapped versions in the App class now contain information about
  the Homebrew installation as well.

* A HomebrewDiagnostics class has been added, which is currently able
  to detect conflicts between the `php` formulae of core and the
  `shivammathur/php` tap (which is currently an issue, see #54)

* Alerts are now displayed as critical if they are truly problematic.

* PhpInstallation was renamed to ActivePhpInstallation, to make room
  for a generic PhpInstallation object which contains cached info.

* Shell.pipe() now returns the contents of standardError if
  standardOutput was empty and there was some data in standardError.
  This makes it easier to debug the output of commands that output to
  standardError. (For example, failed brew commands might.)
2021-11-28 02:20:56 +01:00
52606aae8b 👌 Calling detectPhpVersions always immediately caches the info 2021-11-25 18:49:14 +01:00
2d6ca0f841 Also show full PHP version in dropdown (#53) 2021-11-25 18:41:21 +01:00
34900f929f Use gsed so we can follow symlinks to .ini files (#39, #47) 2021-11-13 21:18:01 +01:00
5dbd05fdfb Add option to auto-restart services (#32) 2021-11-13 20:50:33 +01:00
fe3cf9adb1 Add option to view long PHP version in menu bar 2021-11-13 19:11:05 +01:00
9bc8460cce 👌 Updated notification for Monterey 2021-10-19 21:42:17 +02:00
4cbd2fd6eb 📝 Updated documentation 2021-10-19 00:03:36 +02:00
6fef3fe37a 📝 Updated SECURITY.md 2021-10-19 00:02:13 +02:00
72a20d1ed9 🍱 New build screenshot of Xcode 13.1 2021-10-18 23:54:32 +02:00
73ed80434a 📝 Update README to reflect Monterey compatibility 2021-10-18 23:49:55 +02:00
a78672927b Support for upcoming releases of PHP 8.1 and 8.2 (dev) 2021-10-18 23:48:57 +02:00
4256eae442 👌 CS 2021-10-18 18:48:40 +02:00
76412b68f3 👌 Tests have OS X 10.14 as deployment target too 2021-08-31 11:08:00 +02:00
9153bb140a 👌 Code style fixes (empty line before class closes) 2021-08-31 11:03:55 +02:00
c9c15d10f9 👌 Improve handling of global hotkey load on startup 2021-08-31 10:52:49 +02:00
e8c2277ef5 🐛 Omit initial space (if uncommented, in #45) 2021-06-07 19:18:13 +02:00
23720c5dc9 🐛 Fix #45: Adjusted regex to support spaces 2021-06-07 19:13:52 +02:00
f881f07cba 👌 Cleanup 2021-05-07 15:29:47 +02:00
b072ee8dec 🚚 Improved project organisation, updated README 2021-05-03 16:52:51 +02:00
acfbc0b66f 👌 Clean up how file checks are done 2021-04-27 17:00:56 +02:00
34 changed files with 591 additions and 139 deletions

View File

@@ -18,7 +18,7 @@
C41C1B3E22B0098000E7CF16 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C41C1B3C22B0098000E7CF16 /* Main.storyboard */; }; C41C1B3E22B0098000E7CF16 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C41C1B3C22B0098000E7CF16 /* Main.storyboard */; };
C41C1B4722B009A400E7CF16 /* Shell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4622B009A400E7CF16 /* Shell.swift */; }; C41C1B4722B009A400E7CF16 /* Shell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4622B009A400E7CF16 /* Shell.swift */; };
C41C1B4922B00A9800E7CF16 /* MenuBarImageGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4822B00A9800E7CF16 /* MenuBarImageGenerator.swift */; }; C41C1B4922B00A9800E7CF16 /* MenuBarImageGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4822B00A9800E7CF16 /* MenuBarImageGenerator.swift */; };
C41C1B4B22B019FF00E7CF16 /* PhpInstallation.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4A22B019FF00E7CF16 /* PhpInstallation.swift */; }; C41C1B4B22B019FF00E7CF16 /* ActivePhpInstallation.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4A22B019FF00E7CF16 /* ActivePhpInstallation.swift */; };
C41C1B4D22B0215A00E7CF16 /* Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4C22B0215A00E7CF16 /* Actions.swift */; }; C41C1B4D22B0215A00E7CF16 /* Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4C22B0215A00E7CF16 /* Actions.swift */; };
C41CD0292628D8EE0065BBED /* GlobalKeybindPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41CD0282628D8EE0065BBED /* GlobalKeybindPreference.swift */; }; C41CD0292628D8EE0065BBED /* GlobalKeybindPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41CD0282628D8EE0065BBED /* GlobalKeybindPreference.swift */; };
C42295DD2358D02000E263B2 /* Command.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42295DC2358D02000E263B2 /* Command.swift */; }; C42295DD2358D02000E263B2 /* Command.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42295DC2358D02000E263B2 /* Command.swift */; };
@@ -49,6 +49,10 @@
C4ACA38F25C754C100060C66 /* PhpExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4ACA38E25C754C100060C66 /* PhpExtension.swift */; }; C4ACA38F25C754C100060C66 /* PhpExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4ACA38E25C754C100060C66 /* PhpExtension.swift */; };
C4D8016622B1584700C6DA1B /* Startup.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D8016522B1584700C6DA1B /* Startup.swift */; }; C4D8016622B1584700C6DA1B /* Startup.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D8016522B1584700C6DA1B /* Startup.swift */; };
C4EE188422D3386B00E126E5 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EE188322D3386B00E126E5 /* Constants.swift */; }; C4EE188422D3386B00E126E5 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EE188322D3386B00E126E5 /* Constants.swift */; };
C4F2E4372752F0870020E974 /* HomebrewDiagnostics.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F2E4362752F0870020E974 /* HomebrewDiagnostics.swift */; };
C4F2E4382752F08D0020E974 /* HomebrewDiagnostics.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F2E4362752F0870020E974 /* HomebrewDiagnostics.swift */; };
C4F2E43A2752F7D00020E974 /* PhpInstallation.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F2E4392752F7D00020E974 /* PhpInstallation.swift */; };
C4F2E43B27530F750020E974 /* PhpInstallation.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F2E4392752F7D00020E974 /* PhpInstallation.swift */; };
C4F7809625D7FBF8000DBC97 /* Shell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4622B009A400E7CF16 /* Shell.swift */; }; C4F7809625D7FBF8000DBC97 /* Shell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4622B009A400E7CF16 /* Shell.swift */; };
C4F7809C25D80344000DBC97 /* CommandTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F7809B25D80344000DBC97 /* CommandTest.swift */; }; C4F7809C25D80344000DBC97 /* CommandTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F7809B25D80344000DBC97 /* CommandTest.swift */; };
C4F7809F25D8037C000DBC97 /* Command.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42295DC2358D02000E263B2 /* Command.swift */; }; C4F7809F25D8037C000DBC97 /* Command.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42295DC2358D02000E263B2 /* Command.swift */; };
@@ -70,7 +74,7 @@
C4F780C925D80B75000DBC97 /* StringExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46FA23E246C358E00944F05 /* StringExtension.swift */; }; C4F780C925D80B75000DBC97 /* StringExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46FA23E246C358E00944F05 /* StringExtension.swift */; };
C4F780CA25D80B75000DBC97 /* HomebrewPackage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C412E5FB25700D5300A1FB67 /* HomebrewPackage.swift */; }; C4F780CA25D80B75000DBC97 /* HomebrewPackage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C412E5FB25700D5300A1FB67 /* HomebrewPackage.swift */; };
C4F780CB25D80B75000DBC97 /* StatsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48D0CA225CC992000CC7490 /* StatsView.swift */; }; C4F780CB25D80B75000DBC97 /* StatsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48D0CA225CC992000CC7490 /* StatsView.swift */; };
C4F780CC25D80B75000DBC97 /* PhpInstallation.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4A22B019FF00E7CF16 /* PhpInstallation.swift */; }; C4F780CC25D80B75000DBC97 /* ActivePhpInstallation.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4A22B019FF00E7CF16 /* ActivePhpInstallation.swift */; };
C4F780CD25D80B75000DBC97 /* Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = C476FF9722B0DD830098105B /* Alert.swift */; }; C4F780CD25D80B75000DBC97 /* Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = C476FF9722B0DD830098105B /* Alert.swift */; };
C4F780CE25D80B75000DBC97 /* LocalNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = C474B00524C0E98C00066A22 /* LocalNotification.swift */; }; C4F780CE25D80B75000DBC97 /* LocalNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = C474B00524C0E98C00066A22 /* LocalNotification.swift */; };
C4F8C0A422D4F12C002EFE61 /* DateExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F8C0A322D4F12C002EFE61 /* DateExtension.swift */; }; C4F8C0A422D4F12C002EFE61 /* DateExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F8C0A322D4F12C002EFE61 /* DateExtension.swift */; };
@@ -101,7 +105,7 @@
C41C1B4022B0098000E7CF16 /* phpmon.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = phpmon.entitlements; 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>"; }; C41C1B4622B009A400E7CF16 /* Shell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shell.swift; sourceTree = "<group>"; };
C41C1B4822B00A9800E7CF16 /* MenuBarImageGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuBarImageGenerator.swift; sourceTree = "<group>"; }; C41C1B4822B00A9800E7CF16 /* MenuBarImageGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuBarImageGenerator.swift; sourceTree = "<group>"; };
C41C1B4A22B019FF00E7CF16 /* PhpInstallation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpInstallation.swift; sourceTree = "<group>"; }; C41C1B4A22B019FF00E7CF16 /* ActivePhpInstallation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivePhpInstallation.swift; sourceTree = "<group>"; };
C41C1B4C22B0215A00E7CF16 /* Actions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Actions.swift; sourceTree = "<group>"; }; C41C1B4C22B0215A00E7CF16 /* Actions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Actions.swift; sourceTree = "<group>"; };
C41CD0282628D8EE0065BBED /* GlobalKeybindPreference.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlobalKeybindPreference.swift; sourceTree = "<group>"; }; C41CD0282628D8EE0065BBED /* GlobalKeybindPreference.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlobalKeybindPreference.swift; sourceTree = "<group>"; };
C42295DC2358D02000E263B2 /* Command.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Command.swift; sourceTree = "<group>"; }; C42295DC2358D02000E263B2 /* Command.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Command.swift; sourceTree = "<group>"; };
@@ -129,6 +133,8 @@
C4E713562570150F00007428 /* SECURITY.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = SECURITY.md; sourceTree = "<group>"; }; C4E713562570150F00007428 /* SECURITY.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = SECURITY.md; sourceTree = "<group>"; };
C4E713572570151400007428 /* docs */ = {isa = PBXFileReference; lastKnownFileType = folder; path = docs; sourceTree = "<group>"; }; C4E713572570151400007428 /* docs */ = {isa = PBXFileReference; lastKnownFileType = folder; path = docs; sourceTree = "<group>"; };
C4EE188322D3386B00E126E5 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = "<group>"; }; C4EE188322D3386B00E126E5 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = "<group>"; };
C4F2E4362752F0870020E974 /* HomebrewDiagnostics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomebrewDiagnostics.swift; sourceTree = "<group>"; };
C4F2E4392752F7D00020E974 /* PhpInstallation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpInstallation.swift; sourceTree = "<group>"; };
C4F7807425D7F7E5000DBC97 /* RELEASE.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = RELEASE.md; sourceTree = "<group>"; }; C4F7807425D7F7E5000DBC97 /* RELEASE.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = RELEASE.md; sourceTree = "<group>"; };
C4F7807925D7F84B000DBC97 /* phpmon-tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "phpmon-tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; C4F7807925D7F84B000DBC97 /* phpmon-tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "phpmon-tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
C4F7807D25D7F84B000DBC97 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; C4F7807D25D7F84B000DBC97 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
@@ -170,6 +176,16 @@
path = Preferences; path = Preferences;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
54B20EDF263AA22C00D3250E /* PHP */ = {
isa = PBXGroup;
children = (
C41C1B4A22B019FF00E7CF16 /* ActivePhpInstallation.swift */,
C4F2E4392752F7D00020E974 /* PhpInstallation.swift */,
C4ACA38E25C754C100060C66 /* PhpExtension.swift */,
);
path = PHP;
sourceTree = "<group>";
};
C405A4CD24B9B9070062FAFA /* IAP */ = { C405A4CD24B9B9070062FAFA /* IAP */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@@ -228,10 +244,11 @@
C41E181722CB61EB0072CF09 /* Domain */ = { C41E181722CB61EB0072CF09 /* Domain */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
5420395726135DB800FB00FA /* Preferences */,
C4F7808A25D7F918000DBC97 /* Terminal */,
C4B13B1D25C4915000548C3A /* Core */, C4B13B1D25C4915000548C3A /* Core */,
54B20EDF263AA22C00D3250E /* PHP */,
C4F7808A25D7F918000DBC97 /* Terminal */,
C47331A0247093AC009A0597 /* Menu */, C47331A0247093AC009A0597 /* Menu */,
5420395726135DB800FB00FA /* Preferences */,
C4811D2822D70D9C00B5F6B3 /* Helpers */, C4811D2822D70D9C00B5F6B3 /* Helpers */,
C4F8C0A222D4F100002EFE61 /* Extensions */, C4F8C0A222D4F100002EFE61 /* Extensions */,
); );
@@ -257,6 +274,8 @@
C476FF9722B0DD830098105B /* Alert.swift */, C476FF9722B0DD830098105B /* Alert.swift */,
C41C1B4822B00A9800E7CF16 /* MenuBarImageGenerator.swift */, C41C1B4822B00A9800E7CF16 /* MenuBarImageGenerator.swift */,
C474B00524C0E98C00066A22 /* LocalNotification.swift */, C474B00524C0E98C00066A22 /* LocalNotification.swift */,
C4F2E4362752F0870020E974 /* HomebrewDiagnostics.swift */,
C412E5FB25700D5300A1FB67 /* HomebrewPackage.swift */,
); );
path = Helpers; path = Helpers;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -266,9 +285,8 @@
children = ( children = (
C41C1B3C22B0098000E7CF16 /* Main.storyboard */, C41C1B3C22B0098000E7CF16 /* Main.storyboard */,
C4811D2322D70A4700B5F6B3 /* App.swift */, C4811D2322D70A4700B5F6B3 /* App.swift */,
C41C1B4A22B019FF00E7CF16 /* PhpInstallation.swift */, C4D8016522B1584700C6DA1B /* Startup.swift */,
C4ACA38E25C754C100060C66 /* PhpExtension.swift */, C41C1B4C22B0215A00E7CF16 /* Actions.swift */,
C412E5FB25700D5300A1FB67 /* HomebrewPackage.swift */,
); );
path = Core; path = Core;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -294,8 +312,6 @@
C49EAB45259FC305007F6C3B /* Paths.swift */, C49EAB45259FC305007F6C3B /* Paths.swift */,
C42295DC2358D02000E263B2 /* Command.swift */, C42295DC2358D02000E263B2 /* Command.swift */,
C41C1B4622B009A400E7CF16 /* Shell.swift */, C41C1B4622B009A400E7CF16 /* Shell.swift */,
C4D8016522B1584700C6DA1B /* Startup.swift */,
C41C1B4C22B0215A00E7CF16 /* Actions.swift */,
); );
path = Terminal; path = Terminal;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -431,8 +447,10 @@
C4F8C0A422D4F12C002EFE61 /* DateExtension.swift in Sources */, C4F8C0A422D4F12C002EFE61 /* DateExtension.swift in Sources */,
5420395926135DC100FB00FA /* PrefsVC.swift in Sources */, 5420395926135DC100FB00FA /* PrefsVC.swift in Sources */,
C41C1B4722B009A400E7CF16 /* Shell.swift in Sources */, C41C1B4722B009A400E7CF16 /* Shell.swift in Sources */,
C4F2E43A2752F7D00020E974 /* PhpInstallation.swift in Sources */,
C41C1B4D22B0215A00E7CF16 /* Actions.swift in Sources */, C41C1B4D22B0215A00E7CF16 /* Actions.swift in Sources */,
C48D0CA325CC992000CC7490 /* StatsView.swift in Sources */, C48D0CA325CC992000CC7490 /* StatsView.swift in Sources */,
C4F2E4372752F0870020E974 /* HomebrewDiagnostics.swift in Sources */,
C41CD0292628D8EE0065BBED /* GlobalKeybindPreference.swift in Sources */, C41CD0292628D8EE0065BBED /* GlobalKeybindPreference.swift in Sources */,
C42295DD2358D02000E263B2 /* Command.swift in Sources */, C42295DD2358D02000E263B2 /* Command.swift in Sources */,
C4811D2422D70A4700B5F6B3 /* App.swift in Sources */, C4811D2422D70A4700B5F6B3 /* App.swift in Sources */,
@@ -443,7 +461,7 @@
C412E5FC25700D5300A1FB67 /* HomebrewPackage.swift in Sources */, C412E5FC25700D5300A1FB67 /* HomebrewPackage.swift in Sources */,
C41C1B3722B0097F00E7CF16 /* AppDelegate.swift in Sources */, C41C1B3722B0097F00E7CF16 /* AppDelegate.swift in Sources */,
C42759672627662800093CAE /* NSMenuExtension.swift in Sources */, C42759672627662800093CAE /* NSMenuExtension.swift in Sources */,
C41C1B4B22B019FF00E7CF16 /* PhpInstallation.swift in Sources */, C41C1B4B22B019FF00E7CF16 /* ActivePhpInstallation.swift in Sources */,
C49EAB46259FC305007F6C3B /* Paths.swift in Sources */, C49EAB46259FC305007F6C3B /* Paths.swift in Sources */,
C476FF9822B0DD830098105B /* Alert.swift in Sources */, C476FF9822B0DD830098105B /* Alert.swift in Sources */,
C474B00624C0E98C00066A22 /* LocalNotification.swift in Sources */, C474B00624C0E98C00066A22 /* LocalNotification.swift in Sources */,
@@ -461,13 +479,14 @@
54EAC806262F212B0092D14E /* GlobalKeybindPreference.swift in Sources */, 54EAC806262F212B0092D14E /* GlobalKeybindPreference.swift in Sources */,
C4F780C425D80B75000DBC97 /* MainMenu.swift in Sources */, C4F780C425D80B75000DBC97 /* MainMenu.swift in Sources */,
C4F780C825D80B75000DBC97 /* DateExtension.swift in Sources */, C4F780C825D80B75000DBC97 /* DateExtension.swift in Sources */,
C4F780CC25D80B75000DBC97 /* PhpInstallation.swift in Sources */, C4F780CC25D80B75000DBC97 /* ActivePhpInstallation.swift in Sources */,
C4F780B125D80B4D000DBC97 /* PhpExtension.swift in Sources */, C4F780B125D80B4D000DBC97 /* PhpExtension.swift in Sources */,
C4F780CE25D80B75000DBC97 /* LocalNotification.swift in Sources */, C4F780CE25D80B75000DBC97 /* LocalNotification.swift in Sources */,
C4FBFC532616485F00CDB8E1 /* PhpVersionDetectionTest.swift in Sources */, C4FBFC532616485F00CDB8E1 /* PhpVersionDetectionTest.swift in Sources */,
C43A8A2425D9D20D00591B77 /* BrewJsonParserTest.swift in Sources */, C43A8A2425D9D20D00591B77 /* BrewJsonParserTest.swift in Sources */,
C4F780CA25D80B75000DBC97 /* HomebrewPackage.swift in Sources */, C4F780CA25D80B75000DBC97 /* HomebrewPackage.swift in Sources */,
C4F780C025D80B6E000DBC97 /* Startup.swift in Sources */, C4F780C025D80B6E000DBC97 /* Startup.swift in Sources */,
C4F2E4382752F08D0020E974 /* HomebrewDiagnostics.swift in Sources */,
C4F780AE25D80B37000DBC97 /* ExtensionParserTest.swift in Sources */, C4F780AE25D80B37000DBC97 /* ExtensionParserTest.swift in Sources */,
C4F780C725D80B75000DBC97 /* StatusMenu.swift in Sources */, C4F780C725D80B75000DBC97 /* StatusMenu.swift in Sources */,
C42759682627662800093CAE /* NSMenuExtension.swift in Sources */, C42759682627662800093CAE /* NSMenuExtension.swift in Sources */,
@@ -477,6 +496,7 @@
C4F780BA25D80B62000DBC97 /* AppDelegate.swift in Sources */, C4F780BA25D80B62000DBC97 /* AppDelegate.swift in Sources */,
C4998F0B2617633900B2526E /* PrefsWC.swift in Sources */, C4998F0B2617633900B2526E /* PrefsWC.swift in Sources */,
C4F780A225D804AA000DBC97 /* Paths.swift in Sources */, C4F780A225D804AA000DBC97 /* Paths.swift in Sources */,
C4F2E43B27530F750020E974 /* PhpInstallation.swift in Sources */,
C4F780BD25D80B65000DBC97 /* Constants.swift in Sources */, C4F780BD25D80B65000DBC97 /* Constants.swift in Sources */,
C4F780C325D80B75000DBC97 /* HeaderView.swift in Sources */, C4F780C325D80B75000DBC97 /* HeaderView.swift in Sources */,
C4F7809625D7FBF8000DBC97 /* Shell.swift in Sources */, C4F7809625D7FBF8000DBC97 /* Shell.swift in Sources */,
@@ -639,7 +659,7 @@
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 56; CURRENT_PROJECT_VERSION = 80;
DEVELOPMENT_TEAM = 8M54J5J787; DEVELOPMENT_TEAM = 8M54J5J787;
ENABLE_HARDENED_RUNTIME = YES; ENABLE_HARDENED_RUNTIME = YES;
INFOPLIST_FILE = phpmon/Info.plist; INFOPLIST_FILE = phpmon/Info.plist;
@@ -647,7 +667,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MARKETING_VERSION = 3.4; MARKETING_VERSION = 4.0;
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon; PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
@@ -663,7 +683,7 @@
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 56; CURRENT_PROJECT_VERSION = 80;
DEVELOPMENT_TEAM = 8M54J5J787; DEVELOPMENT_TEAM = 8M54J5J787;
ENABLE_HARDENED_RUNTIME = YES; ENABLE_HARDENED_RUNTIME = YES;
INFOPLIST_FILE = phpmon/Info.plist; INFOPLIST_FILE = phpmon/Info.plist;
@@ -671,7 +691,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MARKETING_VERSION = 3.4; MARKETING_VERSION = 4.0;
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon; PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
@@ -692,7 +712,7 @@
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
"@loader_path/../Frameworks", "@loader_path/../Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = 11.1; MACOSX_DEPLOYMENT_TARGET = 10.14;
PRODUCT_BUNDLE_IDENTIFIER = "com.nicoverbruggen.phpmon-tests"; PRODUCT_BUNDLE_IDENTIFIER = "com.nicoverbruggen.phpmon-tests";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
@@ -713,7 +733,7 @@
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
"@loader_path/../Frameworks", "@loader_path/../Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = 11.1; MACOSX_DEPLOYMENT_TARGET = 10.14;
PRODUCT_BUNDLE_IDENTIFIER = "com.nicoverbruggen.phpmon-tests"; PRODUCT_BUNDLE_IDENTIFIER = "com.nicoverbruggen.phpmon-tests";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;

View File

@@ -21,7 +21,7 @@ PHP Monitor also gives you quick access to various useful functionality (like ac
PHP Monitor is a universal application that runs on Apple Silicon **and** Intel-based Macs. PHP Monitor is a universal application that runs on Apple Silicon **and** Intel-based Macs.
* macOS 10.14 Mojave or higher (works on macOS 11 Big Sur) * macOS 10.14 Mojave or higher (works on macOS 11 Big Sur and macOS 12 Monterey)
* Homebrew is installed in `/usr/local/homebrew` or `/opt/homebrew` * Homebrew is installed in `/usr/local/homebrew` or `/opt/homebrew`
* The brew formula `php` has to be installed (which version is detected) * The brew formula `php` has to be installed (which version is detected)
* Laravel Valet 2.13 or higher * Laravel Valet 2.13 or higher
@@ -30,7 +30,7 @@ _You may need to update your Valet installation to keep everything working if a
## 🚀 How to install ## 🚀 How to install
You can install via Homebrew, or may download the latest [release](https://github.com/nicoverbruggen/phpmon/releases). You can install via Homebrew (recommended), or may download the latest release on GitHub.
To install via Homebrew, run: To install via Homebrew, run:
@@ -73,6 +73,7 @@ If you're still having issues, here's a few common questions & answers, as well
<li>PHP 7.4</li> <li>PHP 7.4</li>
<li>PHP 8.0</li> <li>PHP 8.0</li>
<li>PHP 8.1</li> <li>PHP 8.1</li>
<li>PHP 8.2 (experimental)</li>
</ul> </ul>
For more details, consult the [constants file](https://github.com/nicoverbruggen/phpmon/blob/main/phpmon/Constants.swift#L16) file to see which versions are supported. For more details, consult the [constants file](https://github.com/nicoverbruggen/phpmon/blob/main/phpmon/Constants.swift#L16) file to see which versions are supported.
@@ -274,7 +275,7 @@ Thank you very much for your contributions, kind words and support.
### Loading info about PHP in the background ### Loading info about PHP in the background
This utility runs `php-config --version'` in the background periodically. It also checks your `.ini` files for extensions and loads more information about your limits (memory limit, POST limit, upload limit). This utility runs `php-config --version` in the background periodically. It also checks your `.ini` files for extensions and loads more information about your limits (memory limit, POST limit, upload limit).
In order to save power, this only happens once every 60 seconds. In order to save power, this only happens once every 60 seconds.
@@ -301,7 +302,7 @@ This app isn't very complicated after all. In the end, this just (conveniently)
## 🔧 Build instructions ## 🔧 Build instructions
<img src="./docs/build.png" width="320px" alt="build button in Xcode"/> <img src="./docs/build.png" width="404px" alt="build button in Xcode"/>
If you'd like to build PHP Monitor yourself, you need: If you'd like to build PHP Monitor yourself, you need:

View File

@@ -4,13 +4,16 @@
Generally speaking, only the latest version of **PHP Monitor** is supported: Generally speaking, only the latest version of **PHP Monitor** is supported:
| Version | Apple Silicon | Supported | Supported macOS | Deployment Target | | Version | Apple silicon | Supported | Supported macOS | Deployment Target | Detected PHP Versions |
| ------- | ------------- | ------------------ | ----- | ----- | | ------- | ------------- | ------------------ | ----- | ----- | ----- |
| 3.x | ✅ Universal binary | ✅ | Big Sur (11.0) | macOS 10.14+ | | 4.0 | ✅ Universal binary | ✅ | Big Sur (11.0) and Monterey (12.0) | macOS 10.14+ | PHP 5.6—PHP 8.2 |
| 2.6 | ✅ Universal binary | | Big Sur (11.0) | macOS 10.14+ | | 3.5 | ✅ Universal binary | | Big Sur (11.0) and Monterey (12.0) | macOS 10.14+ | PHP 5.6—PHP 8.2 |
| 2.5 | ✴️ Universal binary<br/>`/usr/local/homebrew` installations only | | Big Sur (11.0)<br/>Catalina (10.15) | macOS 10.14+ | | 3.5 | Universal binary | | Big Sur (11.0) and Monterey (12.0) | macOS 10.14+ | PHP 5.6—PHP 8.2 |
| 2.4 | ✴️ Universal binary<br/>`/usr/local/homebrew` installations only | | Big Sur (11.0)<br/>Catalina (10.15) | macOS 10.14+ | | 3.0—3.4 | Universal binary | | Big Sur (11.0) | macOS 10.14+ | PHP 5.6—PHP 8.1 |
| < 2.4 | Intel binary<br/>`/usr/local/homebrew` installations only | ❌ | Catalina (10.15) | macOS 10.14+ | | 2.6 | ✅ Universal binary | ❌ | Big Sur (11.0) | macOS 10.14+ | PHP 5.6—PHP 8.0 |
| 2.5 | ✴️ Universal binary<br/>`/usr/local/homebrew` installations only | ❌ | Big Sur (11.0)<br/>Catalina (10.15) | macOS 10.14+ | not applicable |
| 2.4 | ✴️ Universal binary<br/>`/usr/local/homebrew` installations only | ❌ | Big Sur (11.0)<br/>Catalina (10.15) | macOS 10.14+ | not applicable |
| < 2.4 | Intel binary<br/>`/usr/local/homebrew` installations only | ❌ | Catalina (10.15) | macOS 10.14+ | not applicable |
## Reporting a vulnerability ## Reporting a vulnerability

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -23,6 +23,9 @@ class BrewJsonParserTest: XCTestCase {
XCTAssertEqual(package.name, "php") XCTAssertEqual(package.name, "php")
XCTAssertEqual(package.full_name, "php") XCTAssertEqual(package.full_name, "php")
XCTAssertEqual(package.aliases.first!, "php@8.0") XCTAssertEqual(package.aliases.first!, "php@8.0")
XCTAssertEqual(package.installed.contains(where: { installed in
installed.version.starts(with: "8.0")
}), true)
} }
} }

View File

@@ -27,11 +27,16 @@ class ExtensionParserTest: XCTestCase {
return ext.name return ext.name
} }
// These 6 should be found
XCTAssertTrue(extensionNames.contains("xdebug")) XCTAssertTrue(extensionNames.contains("xdebug"))
XCTAssertTrue(extensionNames.contains("imagick")) XCTAssertTrue(extensionNames.contains("imagick"))
XCTAssertTrue(extensionNames.contains("sodium-next"))
XCTAssertTrue(extensionNames.contains("opcache")) XCTAssertTrue(extensionNames.contains("opcache"))
XCTAssertTrue(extensionNames.contains("yaml")) XCTAssertTrue(extensionNames.contains("yaml"))
XCTAssertTrue(extensionNames.contains("custom"))
XCTAssertFalse(extensionNames.contains("fake")) XCTAssertFalse(extensionNames.contains("fake"))
XCTAssertFalse(extensionNames.contains("nice"))
} }
func testExtensionStatusIsCorrect() throws { func testExtensionStatusIsCorrect() throws {
@@ -47,7 +52,7 @@ class ExtensionParserTest: XCTestCase {
func testToggleWorksAsExpected() throws { func testToggleWorksAsExpected() throws {
let destination = Utility.copyToTemporaryFile(resourceName: "php", fileExtension: "ini")! let destination = Utility.copyToTemporaryFile(resourceName: "php", fileExtension: "ini")!
let extensions = PhpExtension.load(from: destination) let extensions = PhpExtension.load(from: destination)
XCTAssertEqual(extensions.count, 4) XCTAssertEqual(extensions.count, 6)
// Try to disable xdebug (should be detected first)! // Try to disable xdebug (should be detected first)!
let xdebug = extensions.first! let xdebug = extensions.first!

View File

@@ -1,8 +1,16 @@
# These should be detected
zend_extension="xdebug.so" zend_extension="xdebug.so"
; zend_extension="imagick.so" ; zend_extension="imagick.so"
zend_extension=/opt/homebrew/opt/php/lib/php/20200930/opcache.so zend_extension=/opt/homebrew/opt/php/lib/php/20200930/opcache.so
zend_extension="/opt/homebrew/opt/php/lib/php/20200930/yaml.so" zend_extension="/opt/homebrew/opt/php/lib/php/20200930/yaml.so"
#zend_extension="/opt/homebrew/opt/php/lib/php/20200930/fake.so" ;zend_extension="sodium-next.so"
extension = custom.so
# These should not be detected
#zend_extension="/opt/homebrew/opt/php/lib/php/20200930/commented.so"
hextension = nice.so
[PHP] [PHP]

View File

@@ -18,25 +18,25 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
(invoked by PHP Monitor) shell commands. It is used to (invoked by PHP Monitor) shell commands. It is used to
invoke all commands in this application. invoke all commands in this application.
*/ */
let sharedShell : Shell let sharedShell: Shell
/** /**
The App singleton contains information about the state of The App singleton contains information about the state of
the application and global variables. the application and global variables.
*/ */
let state : App let state: App
/** /**
The MainMenu singleton is responsible for rendering the The MainMenu singleton is responsible for rendering the
menu bar item and its menu, as well as its actions. menu bar item and its menu, as well as its actions.
*/ */
let menu : MainMenu let menu: MainMenu
/** /**
The paths singleton that determines where Homebrew is installed, The paths singleton that determines where Homebrew is installed,
and where to look for binaries. and where to look for binaries.
*/ */
let paths : Paths let paths: Paths
// MARK: - Initializer // MARK: - Initializer
@@ -77,4 +77,5 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
) -> Bool { ) -> Bool {
return true return true
} }
} }

View File

@@ -9,11 +9,21 @@ import Cocoa
class Constants { class Constants {
/**
* The latest PHP version that is considered to be stable at the time of release.
* This version number is currently not used (only as a default fallback).
*/
static let LatestStablePhpVersion = "8.1"
/** /**
* The PHP versions supported by this application. * The PHP versions supported by this application.
* Versions that do not appear in this array are omitted from the list. * Versions that do not appear in this array are omitted from the list.
*/ */
static let SupportedPhpVersions = [ static let SupportedPhpVersions = [
// ====================
// STABLE RELEASES
// ====================
// Versions of PHP that are stable and are supported.
"5.6", "5.6",
"7.0", "7.0",
"7.1", "7.1",
@@ -21,7 +31,16 @@ class Constants {
"7.3", "7.3",
"7.4", "7.4",
"8.0", "8.0",
"8.1" "8.1",
// ====================
// EXPERIMENTAL SUPPORT
// ====================
// Every release that supports the next release will always support the next
// dev release. In this case, that means that the version below is detected.
"8.2"
] ]
} }

View File

@@ -29,9 +29,26 @@ class Actions {
print("The PHP versions that were detected are: \(versionsOnly)") print("The PHP versions that were detected are: \(versionsOnly)")
App.shared.availablePhpVersions = versionsOnly
Actions.extractPhpLongVersions()
return versionsOnly return versionsOnly
} }
/**
This method extracts the PHP full version number after finding the php installation folders.
To be refactored at some later point, I'd like to cache the `PhpInstallation` objects instead of just the version number at some point.
*/
public static func extractPhpLongVersions()
{
var mappedVersions: [String: PhpInstallation] = [:]
App.shared.availablePhpVersions.forEach { version in
mappedVersions[version] = PhpInstallation(version)
}
App.shared.cachedPhpInstallations = mappedVersions
}
/** /**
Extracts valid PHP versions from an array of strings. Extracts valid PHP versions from an array of strings.
This array of strings is usually retrieved from `grep`. This array of strings is usually retrieved from `grep`.
@@ -201,9 +218,14 @@ class Actions {
{ {
// Escape slashes (or `sed` won't work) // Escape slashes (or `sed` won't work)
let e_original = original.replacingOccurrences(of: "/", with: "\\/") let e_original = original.replacingOccurrences(of: "/", with: "\\/")
let e_replacment = replacement.replacingOccurrences(of: "/", with: "\\/") let e_replacement = replacement.replacingOccurrences(of: "/", with: "\\/")
Shell.run("sed -i '' 's/\(e_original)/\(e_replacment)/g' \(file)") // Check if gsed exists; it is able to follow symlinks, which we want to do to toggle the extension
if Shell.fileExists("\(Paths.binPath)/gsed") {
Shell.run("\(Paths.binPath)/gsed -i --follow-symlinks 's/\(e_original)/\(e_replacement)/g' \(file)")
} else {
Shell.run("sed -i '' 's/\(e_original)/\(e_replacement)/g' \(file)")
}
} }
/** /**
@@ -217,4 +239,5 @@ class Actions {
.trimmingCharacters(in: .whitespacesAndNewlines) .trimmingCharacters(in: .whitespacesAndNewlines)
.contains("YES") .contains("YES")
} }
} }

View File

@@ -16,10 +16,12 @@ class App {
loadGlobalHotkey() loadGlobalHotkey()
} }
static var phpInstall: PhpInstallation? { /** Information about the currently linked PHP installation. */
static var phpInstall: ActivePhpInstallation? {
return App.shared.currentInstall return App.shared.currentInstall
} }
/** Whether the app is busy doing something. Used to determine what UI to display. */
static var busy: Bool { static var busy: Bool {
return App.shared.busy return App.shared.busy
} }
@@ -40,13 +42,18 @@ class App {
/** /**
The currently active installation of PHP. The currently active installation of PHP.
*/ */
var currentInstall: PhpInstallation? = nil var currentInstall: ActivePhpInstallation? = nil
/** /**
All available versions of PHP. All available versions of PHP.
*/ */
var availablePhpVersions : [String] = [] var availablePhpVersions : [String] = []
/**
Cached information about the PHP installations; contains only the full version number at this point.
*/
var cachedPhpInstallations : [String: PhpInstallation] = [:]
/** /**
The timer that will periodically fetch the PHP version that is currently active. The timer that will periodically fetch the PHP version that is currently active.
*/ */
@@ -55,7 +62,7 @@ class App {
/** /**
Information we were able to discern from the Homebrew info command (as JSON). Information we were able to discern from the Homebrew info command (as JSON).
*/ */
var brewPhpPackage: HomebrewPackage? = nil { var brewPhpPackage: HomebrewPackage! = nil {
didSet { didSet {
brewPhpVersion = brewPhpPackage!.version brewPhpVersion = brewPhpPackage!.version
} }
@@ -67,10 +74,10 @@ class App {
If you're up to date, `php` will be aliased to the latest version, If you're up to date, `php` will be aliased to the latest version,
but that might not be the case. but that might not be the case.
We'll technically default to version 8.0, but the information should always be loaded We'll technically default to the version in Constants.swift, but the information
from Homebrew itself upon starting the application. should always be loaded from Homebrew itself upon startup.
*/ */
var brewPhpVersion: String = "8.0" var brewPhpVersion: String = Constants.LatestStablePhpVersion
/** /**
The shortcut the user has requested. The shortcut the user has requested.
@@ -83,29 +90,38 @@ class App {
// MARK: - Methods // MARK: - Methods
/**
On startup, the preferences should be loaded from the .plist, and we'll enable the shortcut if it is set.
*/
private func loadGlobalHotkey() { private func loadGlobalHotkey() {
let hotkey = Preferences.preferences[.globalHotkey] as! String? // Make sure we can retrieve the hotkey from preferences; if we cannot, no hotkey is set
if hotkey == nil { guard let hotkey = Preferences.preferences[.globalHotkey] as? String else {
print("No global hotkey loaded")
return return
} }
let keybindPref = GlobalKeybindPreference.fromJson(hotkey!) // Make sure we can parse the JSON into the desired format; if we cannot, no hotkey is set
guard let keybindPref = GlobalKeybindPreference.fromJson(hotkey) else {
if (keybindPref != nil) { print("No global hotkey loaded, could not be parsed!")
self.shortcutHotkey = HotKey(keyCombo: KeyCombo(
carbonKeyCode: keybindPref!.keyCode,
carbonModifiers: keybindPref!.carbonFlags
))
} else {
self.shortcutHotkey = nil self.shortcutHotkey = nil
return
} }
self.shortcutHotkey = HotKey(keyCombo: KeyCombo(
carbonKeyCode: keybindPref.keyCode,
carbonModifiers: keybindPref.carbonFlags
))
} }
/**
Sets up the action that needs to occur when the shortcut key is pressed (open the menu).
*/
private func setupGlobalHotkeyListener() { private func setupGlobalHotkeyListener() {
guard let hotKey = self.shortcutHotkey else { guard let hotkey = self.shortcutHotkey else {
return return
} }
hotKey.keyDownHandler = {
hotkey.keyDownHandler = {
MainMenu.shared.statusItem.button?.performClick(nil) MainMenu.shared.statusItem.button?.performClick(nil)
NSApplication.shared.activate(ignoringOtherApps: true) NSApplication.shared.activate(ignoringOtherApps: true)
} }

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="17701" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES"> <document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="19455" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies> <dependencies>
<deployment identifier="macosx"/> <deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="17701"/> <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="19455"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
<scenes> <scenes>
@@ -84,8 +84,8 @@
<scene sceneID="iyi-IS-7Ps"> <scene sceneID="iyi-IS-7Ps">
<objects> <objects>
<viewController title="Preferences" storyboardIdentifier="preferences" showSeguePresentationStyle="single" id="AW2-rV-rbS" customClass="PrefsVC" customModule="PHP_Monitor" customModuleProvider="target" sceneMemberID="viewController"> <viewController title="Preferences" storyboardIdentifier="preferences" showSeguePresentationStyle="single" id="AW2-rV-rbS" customClass="PrefsVC" customModule="PHP_Monitor" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" wantsLayer="YES" id="Pf1-A5-3Xz"> <view key="view" wantsLayer="YES" misplaced="YES" id="Pf1-A5-3Xz">
<rect key="frame" x="0.0" y="0.0" width="574" height="189"/> <rect key="frame" x="0.0" y="0.0" width="574" height="311"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="GSr-K5-3yw"> <button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="GSr-K5-3yw">
@@ -102,7 +102,7 @@ Gw
</connections> </connections>
</button> </button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="MEf-MN-oXt"> <button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="MEf-MN-oXt">
<rect key="frame" x="148" y="152" width="406" height="18"/> <rect key="frame" x="148" y="274" width="406" height="18"/>
<buttonCell key="cell" type="check" title="DYN_ICON" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="m5s-qp-Iaj"> <buttonCell key="cell" type="check" title="DYN_ICON" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="m5s-qp-Iaj">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/> <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/> <font key="font" metaFont="system"/>
@@ -112,7 +112,7 @@ Gw
</connections> </connections>
</button> </button>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="JrH-aa-AzL"> <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="JrH-aa-AzL">
<rect key="frame" x="148" y="131" width="408" height="14"/> <rect key="frame" x="148" y="253" width="408" height="14"/>
<textFieldCell key="cell" title="DYN_ICON_DESC" id="MHA-Xt-xgF"> <textFieldCell key="cell" title="DYN_ICON_DESC" id="MHA-Xt-xgF">
<font key="font" metaFont="smallSystem"/> <font key="font" metaFont="smallSystem"/>
<color key="textColor" name="secondaryLabelColor" catalog="System" colorSpace="catalog"/> <color key="textColor" name="secondaryLabelColor" catalog="System" colorSpace="catalog"/>
@@ -150,7 +150,7 @@ Gw
<rect key="frame" x="325" y="75" width="138" height="32"/> <rect key="frame" x="325" y="75" width="138" height="32"/>
<buttonCell key="cell" type="push" title="CLEAR_SHORTCUT" bezelStyle="rounded" alignment="center" enabled="NO" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="nvE-5d-VOS"> <buttonCell key="cell" type="push" title="CLEAR_SHORTCUT" bezelStyle="rounded" alignment="center" enabled="NO" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="nvE-5d-VOS">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system" size="11"/> <font key="font" metaFont="smallSystem"/>
</buttonCell> </buttonCell>
<connections> <connections>
<action selector="unregister:" target="AW2-rV-rbS" id="2RI-4w-6Td"/> <action selector="unregister:" target="AW2-rV-rbS" id="2RI-4w-6Td"/>
@@ -165,7 +165,7 @@ Gw
</textFieldCell> </textFieldCell>
</textField> </textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="31d-gd-auR"> <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="31d-gd-auR">
<rect key="frame" x="18" y="153" width="124" height="16"/> <rect key="frame" x="18" y="275" width="124" height="16"/>
<constraints> <constraints>
<constraint firstAttribute="width" constant="120" id="8dt-Pg-wFI"/> <constraint firstAttribute="width" constant="120" id="8dt-Pg-wFI"/>
</constraints> </constraints>
@@ -183,44 +183,106 @@ Gw
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/> <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell> </textFieldCell>
</textField> </textField>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="vSc-oQ-NC5">
<rect key="frame" x="148" y="220" width="121" height="18"/>
<buttonCell key="cell" type="check" title="FULL_PHP_VER" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="eCd-ja-EwE">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="toggledFullPhpVersion:" target="AW2-rV-rbS" id="RCY-Ah-sLM"/>
</connections>
</button>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="t24-LR-wKz">
<rect key="frame" x="148" y="199" width="123" height="14"/>
<textFieldCell key="cell" title="FULL_PHP_VER_DESC" id="8gG-qs-mHR">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="secondaryLabelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ogC-wz-ZfO">
<rect key="frame" x="18" y="153" width="124" height="16"/>
<constraints>
<constraint firstAttribute="width" constant="120" id="i9O-6m-Gr9"/>
</constraints>
<textFieldCell key="cell" lineBreakMode="clipping" alignment="right" title="PREF_SERVICES:" id="bm4-rf-kCF">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="47u-9B-eDu">
<rect key="frame" x="148" y="152" width="126" height="18"/>
<buttonCell key="cell" type="check" title="AUTO_RESTART" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="n1d-l4-inL">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="toggledAutoRestartServices:" target="AW2-rV-rbS" id="THn-nu-IiJ"/>
</connections>
</button>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ObP-GE-ejZ">
<rect key="frame" x="148" y="131" width="126" height="14"/>
<textFieldCell key="cell" title="AUTO_RESTART_DESC" id="F9P-iQ-gBk">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="secondaryLabelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews> </subviews>
<constraints> <constraints>
<constraint firstItem="ogC-wz-ZfO" firstAttribute="trailing" secondItem="31d-gd-auR" secondAttribute="trailing" id="2Lr-Ht-qKI"/>
<constraint firstItem="t24-LR-wKz" firstAttribute="leading" secondItem="vSc-oQ-NC5" secondAttribute="leading" id="3tK-kp-q5R"/>
<constraint firstItem="t24-LR-wKz" firstAttribute="top" secondItem="vSc-oQ-NC5" secondAttribute="bottom" constant="8" symbolic="YES" id="4Ft-lN-vwA"/>
<constraint firstAttribute="trailing" secondItem="JrH-aa-AzL" secondAttribute="trailing" constant="20" symbolic="YES" id="8iM-Xf-ShU"/> <constraint firstAttribute="trailing" secondItem="JrH-aa-AzL" secondAttribute="trailing" constant="20" symbolic="YES" id="8iM-Xf-ShU"/>
<constraint firstItem="ObP-GE-ejZ" firstAttribute="leading" secondItem="47u-9B-eDu" secondAttribute="leading" id="ASF-WR-A3X"/>
<constraint firstAttribute="trailing" secondItem="GSr-K5-3yw" secondAttribute="trailing" constant="20" symbolic="YES" id="AT9-5F-6g1"/> <constraint firstAttribute="trailing" secondItem="GSr-K5-3yw" secondAttribute="trailing" constant="20" symbolic="YES" id="AT9-5F-6g1"/>
<constraint firstItem="YsQ-AZ-Aei" firstAttribute="leading" secondItem="V7b-jv-oCB" secondAttribute="trailing" constant="12" symbolic="YES" id="Bk6-4V-GLk"/> <constraint firstItem="YsQ-AZ-Aei" firstAttribute="leading" secondItem="V7b-jv-oCB" secondAttribute="trailing" constant="12" symbolic="YES" id="Bk6-4V-GLk"/>
<constraint firstItem="31d-gd-auR" firstAttribute="top" secondItem="Pf1-A5-3Xz" secondAttribute="top" constant="20" symbolic="YES" id="C3K-NX-BBl"/> <constraint firstItem="31d-gd-auR" firstAttribute="top" secondItem="Pf1-A5-3Xz" secondAttribute="top" constant="20" symbolic="YES" id="C3K-NX-BBl"/>
<constraint firstItem="YsQ-AZ-Aei" firstAttribute="top" secondItem="V7b-jv-oCB" secondAttribute="top" id="DY5-za-saX"/> <constraint firstItem="YsQ-AZ-Aei" firstAttribute="top" secondItem="V7b-jv-oCB" secondAttribute="top" id="DY5-za-saX"/>
<constraint firstItem="vSc-oQ-NC5" firstAttribute="leading" secondItem="JrH-aa-AzL" secondAttribute="leading" id="FVa-vu-VGJ"/>
<constraint firstItem="MEf-MN-oXt" firstAttribute="leading" secondItem="31d-gd-auR" secondAttribute="trailing" constant="10" id="G5S-JV-re3"/> <constraint firstItem="MEf-MN-oXt" firstAttribute="leading" secondItem="31d-gd-auR" secondAttribute="trailing" constant="10" id="G5S-JV-re3"/>
<constraint firstItem="V7b-jv-oCB" firstAttribute="firstBaseline" secondItem="5ZK-BG-o1t" secondAttribute="firstBaseline" id="H5D-2D-DLH"/> <constraint firstItem="V7b-jv-oCB" firstAttribute="firstBaseline" secondItem="5ZK-BG-o1t" secondAttribute="firstBaseline" id="H5D-2D-DLH"/>
<constraint firstItem="1TO-9H-z2d" firstAttribute="leading" secondItem="V7b-jv-oCB" secondAttribute="leading" id="Imk-o0-2fS"/> <constraint firstItem="1TO-9H-z2d" firstAttribute="leading" secondItem="V7b-jv-oCB" secondAttribute="leading" id="Imk-o0-2fS"/>
<constraint firstItem="ObP-GE-ejZ" firstAttribute="top" secondItem="47u-9B-eDu" secondAttribute="bottom" constant="8" symbolic="YES" id="JqR-Jd-SoR"/>
<constraint firstItem="JrH-aa-AzL" firstAttribute="leading" secondItem="MEf-MN-oXt" secondAttribute="leading" id="K2H-Af-2qK"/> <constraint firstItem="JrH-aa-AzL" firstAttribute="leading" secondItem="MEf-MN-oXt" secondAttribute="leading" id="K2H-Af-2qK"/>
<constraint firstItem="5ZK-BG-o1t" firstAttribute="top" secondItem="JrH-aa-AzL" secondAttribute="bottom" constant="30" id="NMk-yt-fha"/> <constraint firstItem="5ZK-BG-o1t" firstAttribute="top" secondItem="ObP-GE-ejZ" secondAttribute="bottom" constant="30" id="LO4-8j-ihp"/>
<constraint firstItem="47u-9B-eDu" firstAttribute="top" secondItem="ogC-wz-ZfO" secondAttribute="top" id="T9j-v2-fSW"/>
<constraint firstItem="JrH-aa-AzL" firstAttribute="top" secondItem="MEf-MN-oXt" secondAttribute="bottom" constant="8" symbolic="YES" id="Vf8-fx-H50"/> <constraint firstItem="JrH-aa-AzL" firstAttribute="top" secondItem="MEf-MN-oXt" secondAttribute="bottom" constant="8" symbolic="YES" id="Vf8-fx-H50"/>
<constraint firstItem="MEf-MN-oXt" firstAttribute="firstBaseline" secondItem="31d-gd-auR" secondAttribute="firstBaseline" id="W36-bE-iAT"/> <constraint firstItem="MEf-MN-oXt" firstAttribute="firstBaseline" secondItem="31d-gd-auR" secondAttribute="firstBaseline" id="W36-bE-iAT"/>
<constraint firstItem="1TO-9H-z2d" firstAttribute="firstBaseline" secondItem="V7b-jv-oCB" secondAttribute="baseline" constant="25" id="bJG-ed-pch"/> <constraint firstItem="1TO-9H-z2d" firstAttribute="firstBaseline" secondItem="V7b-jv-oCB" secondAttribute="baseline" constant="25" id="bJG-ed-pch"/>
<constraint firstItem="V7b-jv-oCB" firstAttribute="leading" secondItem="JrH-aa-AzL" secondAttribute="leading" id="bUY-uH-N7A"/> <constraint firstItem="V7b-jv-oCB" firstAttribute="leading" secondItem="JrH-aa-AzL" secondAttribute="leading" id="bUY-uH-N7A"/>
<constraint firstItem="5ZK-BG-o1t" firstAttribute="trailing" secondItem="31d-gd-auR" secondAttribute="trailing" id="c4g-jO-JUm"/> <constraint firstItem="5ZK-BG-o1t" firstAttribute="trailing" secondItem="31d-gd-auR" secondAttribute="trailing" id="c4g-jO-JUm"/>
<constraint firstAttribute="bottom" secondItem="GSr-K5-3yw" secondAttribute="bottom" constant="20" symbolic="YES" id="dAS-yW-vua"/> <constraint firstAttribute="bottom" secondItem="GSr-K5-3yw" secondAttribute="bottom" constant="20" symbolic="YES" id="dAS-yW-vua"/>
<constraint firstItem="vSc-oQ-NC5" firstAttribute="top" secondItem="JrH-aa-AzL" secondAttribute="bottom" constant="16" id="hQf-4s-iHn"/>
<constraint firstItem="GSr-K5-3yw" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="Pf1-A5-3Xz" secondAttribute="leading" constant="20" symbolic="YES" id="mTE-WD-54L"/> <constraint firstItem="GSr-K5-3yw" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="Pf1-A5-3Xz" secondAttribute="leading" constant="20" symbolic="YES" id="mTE-WD-54L"/>
<constraint firstItem="47u-9B-eDu" firstAttribute="leading" secondItem="MEf-MN-oXt" secondAttribute="leading" id="n8B-C8-dXs"/>
<constraint firstItem="31d-gd-auR" firstAttribute="leading" secondItem="Pf1-A5-3Xz" secondAttribute="leading" constant="20" symbolic="YES" id="o0J-yT-TDX"/> <constraint firstItem="31d-gd-auR" firstAttribute="leading" secondItem="Pf1-A5-3Xz" secondAttribute="leading" constant="20" symbolic="YES" id="o0J-yT-TDX"/>
<constraint firstItem="ogC-wz-ZfO" firstAttribute="top" secondItem="t24-LR-wKz" secondAttribute="bottom" constant="30" id="oXh-LE-sRS"/>
<constraint firstAttribute="trailing" secondItem="MEf-MN-oXt" secondAttribute="trailing" constant="20" symbolic="YES" id="pJg-zj-cBs"/> <constraint firstAttribute="trailing" secondItem="MEf-MN-oXt" secondAttribute="trailing" constant="20" symbolic="YES" id="pJg-zj-cBs"/>
<constraint firstItem="GSr-K5-3yw" firstAttribute="top" secondItem="1TO-9H-z2d" secondAttribute="bottom" constant="20" id="pMZ-Gx-Jmm"/> <constraint firstItem="GSr-K5-3yw" firstAttribute="top" secondItem="1TO-9H-z2d" secondAttribute="bottom" constant="20" id="pMZ-Gx-Jmm"/>
</constraints> </constraints>
</view> </view>
<connections> <connections>
<outlet property="buttonAutoRestartServices" destination="47u-9B-eDu" id="kyg-BX-PQK"/>
<outlet property="buttonClearShortcut" destination="YsQ-AZ-Aei" id="1xo-hk-HgM"/> <outlet property="buttonClearShortcut" destination="YsQ-AZ-Aei" id="1xo-hk-HgM"/>
<outlet property="buttonClose" destination="GSr-K5-3yw" id="d4I-Cf-gXD"/> <outlet property="buttonClose" destination="GSr-K5-3yw" id="d4I-Cf-gXD"/>
<outlet property="buttonDisplayFullPhpVersion" destination="vSc-oQ-NC5" id="ZLa-Vf-4Dq"/>
<outlet property="buttonDynamicIcon" destination="MEf-MN-oXt" id="qEN-Vg-EZS"/> <outlet property="buttonDynamicIcon" destination="MEf-MN-oXt" id="qEN-Vg-EZS"/>
<outlet property="buttonSetShortcut" destination="V7b-jv-oCB" id="2aS-S4-cKR"/> <outlet property="buttonSetShortcut" destination="V7b-jv-oCB" id="2aS-S4-cKR"/>
<outlet property="labelAutoRestartServices" destination="ObP-GE-ejZ" id="uwY-D7-Uve"/>
<outlet property="labelDisplayFullPhpVersion" destination="t24-LR-wKz" id="wYj-Z0-a3h"/>
<outlet property="labelDynamicIcon" destination="JrH-aa-AzL" id="CFc-fF-oPq"/> <outlet property="labelDynamicIcon" destination="JrH-aa-AzL" id="CFc-fF-oPq"/>
<outlet property="labelShortcut" destination="1TO-9H-z2d" id="paF-hK-78x"/> <outlet property="labelShortcut" destination="1TO-9H-z2d" id="paF-hK-78x"/>
<outlet property="leftLabelDynamicIcon" destination="31d-gd-auR" id="ANZ-Zs-4d7"/> <outlet property="leftLabelDynamicIcon" destination="31d-gd-auR" id="ANZ-Zs-4d7"/>
<outlet property="leftLabelGlobalShortcut" destination="5ZK-BG-o1t" id="73E-9i-cg8"/> <outlet property="leftLabelGlobalShortcut" destination="5ZK-BG-o1t" id="73E-9i-cg8"/>
<outlet property="leftLabelServices" destination="ogC-wz-ZfO" id="BYx-Gv-N1p"/>
</connections> </connections>
</viewController> </viewController>
<customObject id="eQC-8B-FkX" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/> <customObject id="eQC-8B-FkX" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects> </objects>
<point key="canvasLocation" x="264" y="399.5"/> <point key="canvasLocation" x="264" y="457"/>
</scene> </scene>
</scenes> </scenes>
</document> </document>

View File

@@ -119,9 +119,14 @@ class Startup {
DispatchQueue.main.async { [self] in DispatchQueue.main.async { [self] in
// Present the information to the user // Present the information to the user
Alert.notify(message: messageText, info: informativeText) Alert.notify(
message: messageText,
info: informativeText,
style: breaking ? .critical : .warning
)
// Only breaking issues will throw the extra retry modal // Only breaking issues will throw the extra retry modal
breaking ? failureCallback() : () breaking ? failureCallback() : ()
} }
} }
} }

View File

@@ -7,11 +7,12 @@
import Cocoa import Cocoa
extension Date extension Date {
{
func toString() -> String { func toString() -> String {
let dateFormatter = DateFormatter() let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss" dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
return dateFormatter.string(from: self) return dateFormatter.string(from: self)
} }
} }

View File

@@ -33,4 +33,5 @@ extension String {
let end = r.upperBound let end = r.upperBound
return String(self[start ..< end]) return String(self[start ..< end])
} }
} }

View File

@@ -7,17 +7,19 @@
// //
import Foundation import Foundation
import Cocoa import Cocoa
// Adapted from: https://stackoverflow.com/a/46268778 // Adapted from: https://stackoverflow.com/a/46268778
protocol XibLoadable { protocol XibLoadable {
static var xibName: String? { get } static var xibName: String? { get }
static func createFromXib(in bundle: Bundle) -> Self? static func createFromXib(in bundle: Bundle) -> Self?
} }
extension XibLoadable where Self: NSView { extension XibLoadable where Self: NSView {
static var xibName: String? { static var xibName: String? {
return String(describing: Self.self) return String(describing: Self.self)
} }
@@ -30,4 +32,5 @@ extension XibLoadable where Self: NSView {
let views = Array<Any>(results).filter { $0 is Self } let views = Array<Any>(results).filter { $0 is Self }
return views.last as? Self return views.last as? Self
} }
} }

View File

@@ -13,9 +13,11 @@ class Alert {
messageText: String, messageText: String,
informativeText: String, informativeText: String,
buttonTitle: String = "OK", buttonTitle: String = "OK",
secondButtonTitle: String = "" secondButtonTitle: String = "",
style: NSAlert.Style = .informational
) -> Bool { ) -> Bool {
let alert = NSAlert.init() let alert = NSAlert.init()
alert.alertStyle = style
alert.messageText = messageText alert.messageText = messageText
alert.informativeText = informativeText alert.informativeText = informativeText
alert.addButton(withTitle: buttonTitle) alert.addButton(withTitle: buttonTitle)
@@ -25,8 +27,8 @@ class Alert {
return alert.runModal() == .alertFirstButtonReturn return alert.runModal() == .alertFirstButtonReturn
} }
public static func notify(message: String, info: String) { public static func notify(message: String, info: String, style: NSAlert.Style = .informational) {
_ = self.present(messageText: message, informativeText: info, buttonTitle: "OK", secondButtonTitle: "") _ = self.present(messageText: message, informativeText: info, buttonTitle: "OK", secondButtonTitle: "", style: style)
} }
} }

View File

@@ -0,0 +1,70 @@
//
// AliasConflict.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 28/11/2021.
// Copyright © 2021 Nico Verbruggen. All rights reserved.
//
import Foundation
class HomebrewDiagnostics {
enum Errors: String {
case aliasConflict = "alias_conflict"
}
static let shared = HomebrewDiagnostics()
var errors: [HomebrewDiagnostics.Errors] = []
init() {
if self.determineAliasConflicts() {
self.errors.append(.aliasConflict)
}
}
/**
It is possible to have the `shivammathur/php` tap installed, and for the core homebrew information to be outdated.
This will then result in two different aliases claiming to point to the same formula (`php`).
This will break all linking functionality in PHP Monitor, and the user needs to be informed of this.
This check only needs to be performed if the `shivammathur/php` tap is active.
*/
public func determineAliasConflicts() -> Bool
{
let tapAlias = Shell.pipe("\(Paths.brew) info shivammathur/php/php --json")
if tapAlias.contains("brew tap shivammathur/php") || tapAlias.contains("Error") {
print("The user does not appear to have tapped: shivammathur/php")
return false
} else {
print("The user DOES have the following tapped: shivammathur/php")
print("Checking for `php` formula conflicts...")
let tapPhp = try! JSONDecoder().decode(
[HomebrewPackage].self,
from: tapAlias.data(using: .utf8)!
).first!
if tapPhp.version != App.shared.brewPhpVersion {
print("The `php` formula alias seems to be the different between the tap and core. This could be a problem!")
print("Determining whether both of these versions are installed...")
let bothInstalled = App.shared.availablePhpVersions.contains(tapPhp.version)
&& App.shared.availablePhpVersions.contains(App.shared.brewPhpVersion)
if bothInstalled {
print("Both conflicting aliases seem to be installed, warning the user!")
} else {
print("Conflicting aliases are not both installed, seems fine!")
}
return bothInstalled
}
print("All seems to be OK. No conflicts, both are PHP \(tapPhp.version).")
return false
}
}
}

View File

@@ -7,12 +7,23 @@
import Foundation import Foundation
struct HomebrewPackage : Decodable { struct HomebrewPackage: Decodable {
let name: String let name: String
let full_name: String let full_name: String
let aliases: [String] let aliases: [String]
let installed: [HomebrewInstalled]
let linked_keg: String?
public var version: String { public var version: String {
return aliases.first!.replacingOccurrences(of: "php@", with: "") return aliases.first!.replacingOccurrences(of: "php@", with: "")
} }
}
struct HomebrewInstalled: Decodable {
let version: String
let built_as_bottle: Bool
let installed_as_dependency: Bool
let installed_on_request: Bool
} }

View File

@@ -10,6 +10,7 @@ import Foundation
import Cocoa import Cocoa
class HeaderView: NSView, XibLoadable { class HeaderView: NSView, XibLoadable {
@IBOutlet weak var textField: NSTextField! @IBOutlet weak var textField: NSTextField!
static func asMenuItem(text: String) -> NSMenuItem { static func asMenuItem(text: String) -> NSMenuItem {
@@ -20,4 +21,5 @@ class HeaderView: NSView, XibLoadable {
item.target = self item.target = self
return item return item
} }
} }

View File

@@ -41,7 +41,18 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate {
When the environment is all clear and the app can run, let's go. When the environment is all clear and the app can run, let's go.
*/ */
private func onEnvironmentPass() { private func onEnvironmentPass() {
App.shared.availablePhpVersions = Actions.detectPhpVersions() _ = 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() updatePhpVersionInStatusBar()
let installation = App.phpInstall! let installation = App.phpInstall!
@@ -165,7 +176,7 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate {
// MARK: - User Interface // MARK: - User Interface
@objc func updatePhpVersionInStatusBar() { @objc func updatePhpVersionInStatusBar() {
App.shared.currentInstall = PhpInstallation() App.shared.currentInstall = ActivePhpInstallation()
refreshIcon() refreshIcon()
update() update()
} }
@@ -180,7 +191,8 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate {
setStatusBar(image: NSImage(named: NSImage.Name("StatusBarIconStatic"))!) setStatusBar(image: NSImage(named: NSImage.Name("StatusBarIconStatic"))!)
} else { } else {
// The dynamic icon has been requested // The dynamic icon has been requested
setStatusBarImage(version: App.phpInstall!.version.short) let long = Preferences.preferences[.fullPhpVersionDynamicIcon] as! Bool
setStatusBarImage(version: long ? App.phpInstall!.version.long : App.phpInstall!.version.short)
} }
} }
} }
@@ -256,6 +268,10 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate {
@objc func toggleExtension(sender: ExtensionMenuItem) { @objc func toggleExtension(sender: ExtensionMenuItem) {
waitAndExecute { waitAndExecute {
sender.phpExtension?.toggle() sender.phpExtension?.toggle()
if Preferences.preferences[.autoServiceRestartAfterExtensionToggle] as! Bool == true {
Actions.restartPhpFpm()
}
} }
} }

View File

@@ -10,6 +10,7 @@ import Foundation
import Cocoa import Cocoa
class StatsView: NSView, XibLoadable { class StatsView: NSView, XibLoadable {
@IBOutlet weak var titleMemLimit: NSTextField! @IBOutlet weak var titleMemLimit: NSTextField!
@IBOutlet weak var titleMaxPost: NSTextField! @IBOutlet weak var titleMaxPost: NSTextField!
@IBOutlet weak var titleMaxUpload: NSTextField! @IBOutlet weak var titleMaxUpload: NSTextField!
@@ -31,4 +32,5 @@ class StatsView: NSView, XibLoadable {
item.target = self item.target = self
return item return item
} }
} }

View File

@@ -42,15 +42,24 @@ class StatusMenu : NSMenu {
private func addSwitchToPhpMenuItems() { private func addSwitchToPhpMenuItems() {
var shortcutKey = 1 var shortcutKey = 1
for index in (0..<App.shared.availablePhpVersions.count).reversed() { for index in (0..<App.shared.availablePhpVersions.count).reversed() {
let version = App.shared.availablePhpVersions[index]
// Get the short and long version
let shortVersion = App.shared.availablePhpVersions[index]
let longVersion = App.shared.cachedPhpInstallations[shortVersion]!.longVersion
let long = Preferences.preferences[.fullPhpVersionDynamicIcon] as! Bool
let versionString = long ? longVersion : shortVersion
let action = #selector(MainMenu.switchToPhpVersion(sender:)) let action = #selector(MainMenu.switchToPhpVersion(sender:))
let brew = (version == App.shared.brewPhpVersion) ? "php" : "php@\(version)" let brew = (shortVersion == App.shared.brewPhpVersion) ? "php" : "php@\(shortVersion)"
let menuItem = PhpMenuItem( let menuItem = PhpMenuItem(
title: "\("mi_php_switch".localized) \(version) (\(brew))", title: "\("mi_php_switch".localized) \(versionString) (\(brew))",
action: (version == App.phpInstall?.version.short) ? nil : action, keyEquivalent: "\(shortcutKey)" action: (shortVersion == App.phpInstall?.version.short) ? nil : action, keyEquivalent: "\(shortcutKey)"
) )
menuItem.version = version
menuItem.version = shortVersion
shortcutKey = shortcutKey + 1 shortcutKey = shortcutKey + 1
self.addItem(menuItem) self.addItem(menuItem)
} }
} }

View File

@@ -1,5 +1,5 @@
// //
// PhpInstallation.swift // ActivePhpInstallation.swift
// PHP Monitor // PHP Monitor
// //
// Copyright © 2021 Nico Verbruggen. All rights reserved. // Copyright © 2021 Nico Verbruggen. All rights reserved.
@@ -7,7 +7,15 @@
import Foundation import Foundation
class PhpInstallation { /**
An installed version of PHP, that was detected by scanning the `/opt/php@version/bin` directory.
When initialized, that version's .ini files are also scanned (for active or inactive extensions).
Integrity checks can be performed to determine whether PHP-FPM is configured correctly.
- Note: Each installation has a separate version number. Using `version.short` is advisable if you want to interact with Homebrew.
*/
class ActivePhpInstallation {
var version: Version! var version: Version!
var configuration: Configuration! var configuration: Configuration!
@@ -111,17 +119,33 @@ class PhpInstallation {
return (match == nil) ? "⚠️" : "\(value)B" return (match == nil) ? "⚠️" : "\(value)B"
} }
/**
It is always possible that the system configuration for PHP-FPM has not been set up for Valet.
This can occur when a user manually installs a new PHP version, but does not run `valet install`.
In that case, we should alert the user!
- Important: The underlying check is `checkPhpFpmStatus`, which can be run multiple times.
This method actively presents a modal if said checks fails, so don't call this method too many times.
*/
public func notifyAboutBrokenPhpFpm() { public func notifyAboutBrokenPhpFpm() {
if !self.checkPhpFpmStatus() { if !self.checkPhpFpmStatus() {
DispatchQueue.main.async { DispatchQueue.main.async {
Alert.notify( Alert.notify(
message: "alert.php_fpm_broken.title".localized, message: "alert.php_fpm_broken.title".localized,
info: "alert.php_fpm_broken.info".localized info: "alert.php_fpm_broken.info".localized,
style: .critical
) )
} }
} }
} }
/**
Determine if PHP-FPM is configured correctly.
For PHP 5.6, we'll check if `valet.sock` is included in the main `php-fpm.conf` file, but for more recent
versions of PHP, we can just check for the existence of the `valet-fpm.conf` file. If the check here fails,
that means that Valet won't work properly.
*/
private func checkPhpFpmStatus() -> Bool { private func checkPhpFpmStatus() -> Bool {
if self.version.short == "5.6" { if self.version.short == "5.6" {
// The main PHP config file should contain `valet.sock` and then we're probably fine? // The main PHP config file should contain `valet.sock` and then we're probably fine?
@@ -146,4 +170,5 @@ class PhpInstallation {
var upload_max_filesize = "???" var upload_max_filesize = "???"
var post_max_size = "???" var post_max_size = "???"
} }
} }

View File

@@ -46,7 +46,7 @@ class PhpExtension {
- Note: Extensions that are disabled in a different way will not be detected. This is intentional. - Note: Extensions that are disabled in a different way will not be detected. This is intentional.
*/ */
static let extensionRegex = #"^(extension=|zend_extension=|; extension=|; zend_extension=)(?<name>["]?(?:\/?.\/?)+(?:\.so)"?)$"# static let extensionRegex = #"^(extension|zend_extension|;(\s?)extension|;(\s?)zend_extension)(\s?)(=)(\s?)(?<name>["]?(?:\/?.\/?)+(?:\.so)"?)$"#
/** /**
When registering an extension, we do that based on the line found inside the .ini file. When registering an extension, we do that based on the line found inside the .ini file.
@@ -61,6 +61,7 @@ class PhpExtension {
let fullPath = String(line[range]) let fullPath = String(line[range])
.replacingOccurrences(of: "\"", with: "") // replace excess " .replacingOccurrences(of: "\"", with: "") // replace excess "
.replacingOccurrences(of: ".so", with: "") // replace excess .so .replacingOccurrences(of: ".so", with: "") // replace excess .so
self.name = String(fullPath.split(separator: "/").last!) // take last segment self.name = String(fullPath.split(separator: "/").last!) // take last segment
self.enabled = !line.contains(";") self.enabled = !line.contains(";")
@@ -71,12 +72,15 @@ class PhpExtension {
This simply toggles the extension in the .ini file. You may need to restart the other services in order for this change to apply. This simply toggles the extension in the .ini file. You may need to restart the other services in order for this change to apply.
*/ */
func toggle() { func toggle() {
Actions.sed( let newLine = enabled
file: file, // DISABLED: Commented out line
original: line, ? "; \(line)"
replacement: enabled ? "; \(line)" : line.replacingOccurrences(of: "; ", with: "") // ENABLED: Line where the comment delimiter (;) is removed
) : line.replacingOccurrences(of: "; ", with: "")
enabled = !enabled
Actions.sed(file: file, original: line, replacement: newLine)
enabled.toggle()
} }
// MARK: - Static Methods // MARK: - Static Methods
@@ -93,11 +97,12 @@ class PhpExtension {
} }
return file!.components(separatedBy: "\n") return file!.components(separatedBy: "\n")
.filter({ (line) -> Bool in .filter {
return line.range(of: Self.extensionRegex, options: .regularExpression) != nil return $0.range(of: Self.extensionRegex, options: .regularExpression) != nil
}) }
.map { (line) -> PhpExtension in .map {
return PhpExtension(line, file: path.path) return PhpExtension($0, file: path.path)
} }
} }
} }

View File

@@ -0,0 +1,33 @@
//
// BrewPhpInstallation.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 28/11/2021.
// Copyright © 2021 Nico Verbruggen. All rights reserved.
//
import Foundation
class PhpInstallation {
var longVersion: String
var homebrewInfo: HomebrewPackage
init(_ version: String) {
let phpConfigExecutablePath = "\(Paths.optPath)/php@\(version)/bin/php-config"
self.longVersion = version
if Shell.fileExists(phpConfigExecutablePath) {
self.longVersion = Command.execute(
path: phpConfigExecutablePath,
arguments: ["--version"]
)
}
let info = Shell.pipe("\(Paths.brew) info php@\(version) --json")
self.homebrewInfo = try! JSONDecoder().decode(
[HomebrewPackage].self,
from: info.data(using: .utf8)!
).first!
}
}

View File

@@ -9,39 +9,71 @@
import Foundation import Foundation
enum PreferenceName: String { enum PreferenceName: String {
case wasLaunchedBefore = "launched_before"
case shouldDisplayDynamicIcon = "use_dynamic_icon" case shouldDisplayDynamicIcon = "use_dynamic_icon"
case fullPhpVersionDynamicIcon = "full_php_in_menu_bar"
case autoServiceRestartAfterExtensionToggle = "auto_restart_after_extension_toggle"
case globalHotkey = "global_hotkey" case globalHotkey = "global_hotkey"
} }
class Preferences { class Preferences {
static func handleFirstTimeLaunch() { // MARK: - Singleton
let launchedBefore = UserDefaults.standard.bool(forKey: "launched_before")
if launchedBefore { static var shared = Preferences()
var cachedPreferences: [PreferenceName: Any?]
public init() {
Preferences.handleFirstTimeLaunch()
self.cachedPreferences = Self.cache()
}
// MARK: - First Time Run
/**
Note: macOS seems to cache plist values in memory as well as in files.
You can find the persisted configuration file in: ~/Library/Preferences/com.nicoverbruggen.phpmon.plist
To clear the cache, and get a first-run experience you may need to run:
```
defaults delete com.nicoverbruggen.phpmon
killall cfprefsd
```
*/
static func handleFirstTimeLaunch() {
UserDefaults.standard.register(defaults: [
PreferenceName.shouldDisplayDynamicIcon.rawValue: true,
PreferenceName.fullPhpVersionDynamicIcon.rawValue: false,
PreferenceName.autoServiceRestartAfterExtensionToggle.rawValue: true
])
if UserDefaults.standard.bool(forKey: PreferenceName.wasLaunchedBefore.rawValue) {
return return
} }
UserDefaults.standard.setValue(true, forKey: PreferenceName.shouldDisplayDynamicIcon.rawValue)
UserDefaults.standard.setValue(true, forKey: "launched_before")
UserDefaults.standard.synchronize()
print("Saving first-time preferences!") print("Saving first-time preferences!")
UserDefaults.standard.setValue(true, forKey: PreferenceName.wasLaunchedBefore.rawValue)
UserDefaults.standard.synchronize()
} }
static func retrieve() -> [PreferenceName: Any] { // MARK: - API
Preferences.handleFirstTimeLaunch()
return [
.shouldDisplayDynamicIcon: UserDefaults.standard.bool(
forKey: PreferenceName.shouldDisplayDynamicIcon.rawValue) as Any,
.globalHotkey: UserDefaults.standard.string(
forKey: PreferenceName.globalHotkey.rawValue) as Any
]
}
static var preferences: [PreferenceName: Any?] { static var preferences: [PreferenceName: Any?] {
return Preferences.retrieve() return Self.shared.cachedPreferences
}
// MARK: - Internal Functionality
static func cache() -> [PreferenceName: Any] {
return [
// Part 1: Always Booleans
.shouldDisplayDynamicIcon: UserDefaults.standard.bool(forKey: PreferenceName.shouldDisplayDynamicIcon.rawValue) as Any,
.fullPhpVersionDynamicIcon: UserDefaults.standard.bool(forKey: PreferenceName.fullPhpVersionDynamicIcon.rawValue) as Any,
.autoServiceRestartAfterExtensionToggle: UserDefaults.standard.bool(forKey: PreferenceName.autoServiceRestartAfterExtensionToggle.rawValue) as Any,
// Part 2: Always Strings
.globalHotkey: UserDefaults.standard.string(forKey: PreferenceName.globalHotkey.rawValue) as Any,
]
} }
static func update(_ preference: PreferenceName, value: Any?) { static func update(_ preference: PreferenceName, value: Any?) {
@@ -51,6 +83,9 @@ class Preferences {
UserDefaults.standard.setValue(value, forKey: preference.rawValue) UserDefaults.standard.setValue(value, forKey: preference.rawValue)
} }
UserDefaults.standard.synchronize() UserDefaults.standard.synchronize()
// Update the preferences cache in memory!
Preferences.shared.cachedPreferences = Preferences.cache()
} }
} }

View File

@@ -12,17 +12,31 @@ import Carbon
class PrefsVC: NSViewController { class PrefsVC: NSViewController {
// Labels on the left
@IBOutlet weak var leftLabelDynamicIcon: NSTextField! @IBOutlet weak var leftLabelDynamicIcon: NSTextField!
@IBOutlet weak var leftLabelServices: NSTextField!
@IBOutlet weak var leftLabelGlobalShortcut: NSTextField! @IBOutlet weak var leftLabelGlobalShortcut: NSTextField!
// Dynamic icon
@IBOutlet weak var buttonDynamicIcon: NSButton! @IBOutlet weak var buttonDynamicIcon: NSButton!
@IBOutlet weak var labelDynamicIcon: NSTextField! @IBOutlet weak var labelDynamicIcon: NSTextField!
@IBOutlet weak var buttonClose: NSButton!
// Full PHP version
@IBOutlet weak var buttonDisplayFullPhpVersion: NSButton!
@IBOutlet weak var labelDisplayFullPhpVersion: NSTextField!
// Auto-restart services
@IBOutlet weak var buttonAutoRestartServices: NSButton!
@IBOutlet weak var labelAutoRestartServices: NSTextField!
// Shortcut
@IBOutlet weak var buttonSetShortcut: NSButton! @IBOutlet weak var buttonSetShortcut: NSButton!
@IBOutlet weak var buttonClearShortcut: NSButton! @IBOutlet weak var buttonClearShortcut: NSButton!
@IBOutlet weak var labelShortcut: NSTextField! @IBOutlet weak var labelShortcut: NSTextField!
// Close button (bottom right)
@IBOutlet weak var buttonClose: NSButton!
// MARK: - Display // MARK: - Display
public static func show(delegate: NSWindowDelegate? = nil) { public static func show(delegate: NSWindowDelegate? = nil) {
@@ -47,6 +61,7 @@ class PrefsVC: NSViewController {
override func viewWillAppear() { override func viewWillAppear() {
loadLocalization() loadLocalization()
loadDynamicIconFromPreferences() loadDynamicIconFromPreferences()
loadFullPhpVersionFromPreferences()
loadGlobalKeybindFromPreferences() loadGlobalKeybindFromPreferences()
} }
@@ -62,6 +77,15 @@ class PrefsVC: NSViewController {
labelDynamicIcon.stringValue = "prefs.dynamic_icon_desc".localized labelDynamicIcon.stringValue = "prefs.dynamic_icon_desc".localized
buttonDynamicIcon.title = "prefs.dynamic_icon_title".localized buttonDynamicIcon.title = "prefs.dynamic_icon_title".localized
// Full PHP version
buttonDisplayFullPhpVersion.title = "prefs.display_full_php_version".localized
labelDisplayFullPhpVersion.stringValue = "prefs.display_full_php_version_desc".localized
// Services
leftLabelServices.stringValue = "prefs.services".localized
buttonAutoRestartServices.title = "prefs.auto_restart_services_title".localized
labelAutoRestartServices.stringValue = "prefs_auto_restart_services_desc".localized
// Global Shortcut // Global Shortcut
leftLabelGlobalShortcut.stringValue = "prefs.global_shortcut".localized leftLabelGlobalShortcut.stringValue = "prefs.global_shortcut".localized
labelShortcut.stringValue = "prefs.shortcut_desc".localized labelShortcut.stringValue = "prefs.shortcut_desc".localized
@@ -72,13 +96,40 @@ class PrefsVC: NSViewController {
buttonClose.title = "prefs.close".localized buttonClose.title = "prefs.close".localized
} }
// MARK: - Dynamic Icon Preference // MARK: - Loading Preferences
func loadDynamicIconFromPreferences() { func loadDynamicIconFromPreferences() {
let shouldDisplay = Preferences.preferences[.shouldDisplayDynamicIcon] as! Bool == true let shouldDisplay = Preferences.preferences[.shouldDisplayDynamicIcon] as! Bool == true
self.buttonDynamicIcon.state = shouldDisplay ? .on : .off self.buttonDynamicIcon.state = shouldDisplay ? .on : .off
} }
func loadFullPhpVersionFromPreferences() {
let shouldDisplay = Preferences.preferences[.fullPhpVersionDynamicIcon] as! Bool == true
self.buttonDisplayFullPhpVersion.state = shouldDisplay ? .on : .off
}
func loadAutoRestartServicesFromPreferences() {
let shouldDisplay = Preferences.preferences[.autoServiceRestartAfterExtensionToggle] as! Bool == true
self.buttonAutoRestartServices.state = shouldDisplay ? .on : .off
}
// MARK: - Actions
@IBAction func toggledDynamicIcon(_ sender: Any) {
Preferences.update(.shouldDisplayDynamicIcon, value: buttonDynamicIcon.state == .on)
MainMenu.shared.refreshIcon()
}
@IBAction func toggledFullPhpVersion(_ sender: Any) {
Preferences.update(.fullPhpVersionDynamicIcon, value: buttonDisplayFullPhpVersion.state == .on)
MainMenu.shared.refreshIcon()
MainMenu.shared.update()
}
@IBAction func toggledAutoRestartServices(_ sender: Any) {
Preferences.update(.autoServiceRestartAfterExtensionToggle, value: buttonAutoRestartServices.state == .on)
}
// MARK: - Shortcut Preference // MARK: - Shortcut Preference
// Adapted from: https://dev.to/mitchartemis/creating-a-global-configurable-shortcut-for-macos-apps-in-swift-25e9 // Adapted from: https://dev.to/mitchartemis/creating-a-global-configurable-shortcut-for-macos-apps-in-swift-25e9
@@ -166,13 +217,6 @@ class PrefsVC: NSViewController {
buttonSetShortcut.title = globalKeybindPreference.description buttonSetShortcut.title = globalKeybindPreference.description
} }
// MARK: - Actions
@IBAction func toggledDynamicIcon(_ sender: Any) {
Preferences.update(.shouldDisplayDynamicIcon, value: buttonDynamicIcon.state == .on)
MainMenu.shared.refreshIcon()
}
@IBAction func pressed(_ sender: Any) { @IBAction func pressed(_ sender: Any) {
self.view.window?.windowController?.close() self.view.window?.windowController?.close()
} }

View File

@@ -14,6 +14,7 @@ struct Keys {
} }
class PrefsWC: NSWindowController { class PrefsWC: NSWindowController {
override func windowDidLoad() { override func windowDidLoad() {
super.windowDidLoad() super.windowDidLoad()
} }

View File

@@ -34,7 +34,7 @@ class Command {
.joined(separator: "\n") .joined(separator: "\n")
} }
return output; return output
} }
} }

View File

@@ -64,4 +64,5 @@ class Paths {
public static var etcPath: String { public static var etcPath: String {
return "\(shared.baseDir.rawValue)/etc" return "\(shared.baseDir.rawValue)/etc"
} }
} }

View File

@@ -21,7 +21,7 @@ class Shell {
// MARK: - Singleton // MARK: - Singleton
var shell = "/bin/sh" var shell: String
init() { init() {
// Determine if we're using macOS Catalina or newer (that support /bin/zsh as default shell) // Determine if we're using macOS Catalina or newer (that support /bin/zsh as default shell)
@@ -29,9 +29,14 @@ class Shell {
.init(majorVersion: 10, minorVersion: 15, patchVersion: 0)) .init(majorVersion: 10, minorVersion: 15, patchVersion: 0))
// If macOS Mojave is being used, we'll default to /bin/bash // If macOS Mojave is being used, we'll default to /bin/bash
shell = at_least_10_15 ? "/bin/sh" : "/bin/bash" shell = at_least_10_15
print(at_least_10_15 ? "Detected recent macOS (> 10.15): defaulting to /bin/sh" ? "/bin/sh"
: "Detected older macOS (< 10.15): so defaulting to /bin/bash") : "/bin/bash"
print(at_least_10_15
? "Detected recent macOS (> 10.15): defaulting to /bin/sh"
: "Detected older macOS (< 10.15): defaulting to /bin/bash"
)
} }
/** /**
@@ -58,25 +63,31 @@ class Shell {
*/ */
func pipe(_ command: String) -> String { func pipe(_ command: String) -> String {
let task = Process() let task = Process()
let pipe = Pipe() let outputPipe = Pipe()
let errorPipe = Pipe()
task.launchPath = self.shell task.launchPath = self.shell
task.arguments = ["--login", "-c", command] task.arguments = ["--login", "-c", command]
task.standardOutput = pipe task.standardOutput = outputPipe
task.standardError = errorPipe
task.launch() task.launch()
return String( let error = String(data: errorPipe.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8)!
data: pipe.fileHandleForReading.readDataToEndOfFile(), let output = String(data: outputPipe.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8)!
encoding: .utf8
)! if (output == "" && error.lengthOfBytes(using: .utf8) > 0) {
return error
}
return output
} }
/** /**
Checks if a file exists at the provided path. Checks if a file exists at the provided path.
Uses `/bin/echo` instead of the `builtin` (which does not support `-n`).
*/ */
public static func fileExists(_ path: String) -> Bool { public static func fileExists(_ path: String) -> Bool {
return Shell.pipe( return Shell.pipe("if [ -f \(path) ]; then /bin/echo -n \"0\"; fi") == "0"
"if [ -f \(path) ]; then echo \"PHP_Y_FE\"; fi"
).contains("PHP_Y_FE")
} }
} }

View File

@@ -52,10 +52,17 @@
"prefs.global_shortcut" = "Global shortcut:"; "prefs.global_shortcut" = "Global shortcut:";
"prefs.dynamic_icon" = "Dynamic icon:"; "prefs.dynamic_icon" = "Dynamic icon:";
"prefs.services" = "Services:";
"prefs.auto_restart_services_title" = "Auto-restart PHP-FPM";
"prefs_auto_restart_services_desc" = "When checked, will automatically restart PHP-FPM when\nyou check or uncheck an extension. Slightly slower when enabled, \nbut this applies the extension change immediately for all sites \nyou're serving, no need to restart PHP-FPM manually.";
"prefs.dynamic_icon_title" = "Display dynamic icon in menu bar"; "prefs.dynamic_icon_title" = "Display dynamic icon in menu bar";
"prefs.dynamic_icon_desc" = "If you uncheck this box, the truck icon will always be visible.\nIf checked, it will display the major version number of the\ncurrently linked PHP version."; "prefs.dynamic_icon_desc" = "If you uncheck this box, the truck icon will always be visible.\nIf checked, it will display the major version number of the\ncurrently linked PHP version.";
"prefs.display_full_php_version" = "Display full PHP version in menu bar";
"prefs.display_full_php_version_desc" = "Display the full version instead of the major version only.\n(This may be undesirable on smaller displays,\nso this is disabled by default.)";
"prefs.shortcut_set" = "Set global shortcut"; "prefs.shortcut_set" = "Set global shortcut";
"prefs.shortcut_listening" = "<listening for keypress>"; "prefs.shortcut_listening" = "<listening for keypress>";
"prefs.shortcut_clear" = "Clear"; "prefs.shortcut_clear" = "Clear";
@@ -64,7 +71,10 @@
// NOTIFICATIONS // NOTIFICATIONS
"notification.version_changed_title" = "PHP %@ now active"; "notification.version_changed_title" = "PHP %@ now active";
"notification.version_changed_desc" = "PHP Monitor has finished the switch to PHP %@."; "notification.version_changed_desc" = "PHP Monitor has switched to PHP %@.";
"notification.php_fpm_restarted" = "PHP-FPM automatically restarted";
"notification.php_fpm_restarted_desc" = "You toggled an extension, so PHP-FPM was automatically restarted.";
"notification.services_stopped" = "Valet services stopped"; "notification.services_stopped" = "Valet services stopped";
"notification.services_stopped_desc" = "All services have been successfully stopped."; "notification.services_stopped_desc" = "All services have been successfully stopped.";
@@ -92,6 +102,10 @@
"alert.cannot_start.close" = "Close"; "alert.cannot_start.close" = "Close";
"alert.cannot_start.retry" = "Retry"; "alert.cannot_start.retry" = "Retry";
// PHP alias issue
"alert.php_alias_conflict.title" = "Homebrew `php` formula alias conflict detected";
"alert.php_alias_conflict.info" = "PHP Monitor has detected conflicting `php` aliases in your Homebrew setup, both of which have been detected as installed.\n\nThis will likely result in failed linking when switching PHP versions, and will break PHP Monitor functionality.\n\nFor more information, please visit: https://github.com/nicoverbruggen/phpmon/issues/54";
// STARTUP // STARTUP
/// 1. PHP binary not found /// 1. PHP binary not found