Compare commits
44 Commits
Author | SHA1 | Date | |
---|---|---|---|
c738a03934 | |||
84d62f3583 | |||
f9faa03b92 | |||
55f6c3c6cd | |||
a0c6753761 | |||
327125608a | |||
6c0045302b | |||
9c85bebe72 | |||
fb56cd551e | |||
e83d507e79 | |||
0c0e7fc87d | |||
faf49fbe1d | |||
2925b0ff79 | |||
acb18474c8 | |||
ed61490398 | |||
abb76273c9 | |||
2f15af4ff8 | |||
e29e8416d5 | |||
5d423210dd | |||
340c36fdf8 | |||
3085158b80 | |||
c2585f9bf4 | |||
d478137742 | |||
827bd182b1 | |||
f7500637fe | |||
7c884610b1 | |||
d3d219751e | |||
16d2e7d06f | |||
47b86ff9fa | |||
6e574b9154 | |||
485001403d | |||
694c5e7f7d | |||
d9ff26385a | |||
0cfb7c65bb | |||
3cbc2a0367 | |||
7733c90206 | |||
0ad6e5cb1c | |||
8a47cfd5f1 | |||
db4c0399fd | |||
b0f29b72cd | |||
98a1622ccc | |||
a6ac737590 | |||
bba719012f | |||
cdc0cea01c |
1
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
custom: ['https://nicoverbruggen.be/sponsor', 'https://paypal.me/nicoverbruggen']
|
@@ -3,10 +3,13 @@
|
|||||||
archiveVersion = 1;
|
archiveVersion = 1;
|
||||||
classes = {
|
classes = {
|
||||||
};
|
};
|
||||||
objectVersion = 50;
|
objectVersion = 52;
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
|
5420395926135DC100FB00FA /* PrefsVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5420395826135DC100FB00FA /* PrefsVC.swift */; };
|
||||||
|
5420395F2613607600FB00FA /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5420395E2613607600FB00FA /* Preferences.swift */; };
|
||||||
|
54EAC806262F212B0092D14E /* GlobalKeybindPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41CD0282628D8EE0065BBED /* GlobalKeybindPreference.swift */; };
|
||||||
C405A4D024B9B9140062FAFA /* InternetAccessPolicy.strings in Resources */ = {isa = PBXBuildFile; fileRef = C405A4CE24B9B9130062FAFA /* InternetAccessPolicy.strings */; };
|
C405A4D024B9B9140062FAFA /* InternetAccessPolicy.strings in Resources */ = {isa = PBXBuildFile; fileRef = C405A4CE24B9B9130062FAFA /* InternetAccessPolicy.strings */; };
|
||||||
C405A4D124B9B9140062FAFA /* InternetAccessPolicy.plist in Resources */ = {isa = PBXBuildFile; fileRef = C405A4CF24B9B9140062FAFA /* InternetAccessPolicy.plist */; };
|
C405A4D124B9B9140062FAFA /* InternetAccessPolicy.plist in Resources */ = {isa = PBXBuildFile; fileRef = C405A4CF24B9B9140062FAFA /* InternetAccessPolicy.plist */; };
|
||||||
C412E5FC25700D5300A1FB67 /* HomebrewPackage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C412E5FB25700D5300A1FB67 /* HomebrewPackage.swift */; };
|
C412E5FC25700D5300A1FB67 /* HomebrewPackage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C412E5FB25700D5300A1FB67 /* HomebrewPackage.swift */; };
|
||||||
@@ -17,7 +20,11 @@
|
|||||||
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 /* PhpInstallation.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4A22B019FF00E7CF16 /* PhpInstallation.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 */; };
|
||||||
C42295DD2358D02000E263B2 /* Command.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42295DC2358D02000E263B2 /* Command.swift */; };
|
C42295DD2358D02000E263B2 /* Command.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42295DC2358D02000E263B2 /* Command.swift */; };
|
||||||
|
C4232EE52612526500158FC6 /* Credits.html in Resources */ = {isa = PBXBuildFile; fileRef = C4232EE42612526500158FC6 /* Credits.html */; };
|
||||||
|
C42759672627662800093CAE /* NSMenuExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42759662627662800093CAE /* NSMenuExtension.swift */; };
|
||||||
|
C42759682627662800093CAE /* NSMenuExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42759662627662800093CAE /* NSMenuExtension.swift */; };
|
||||||
C43A8A1A25D9CD1000591B77 /* Utility.swift in Sources */ = {isa = PBXBuildFile; fileRef = C43A8A1925D9CD1000591B77 /* Utility.swift */; };
|
C43A8A1A25D9CD1000591B77 /* Utility.swift in Sources */ = {isa = PBXBuildFile; fileRef = C43A8A1925D9CD1000591B77 /* Utility.swift */; };
|
||||||
C43A8A2025D9D1D700591B77 /* brew.json in Resources */ = {isa = PBXBuildFile; fileRef = C43A8A1F25D9D1D700591B77 /* brew.json */; };
|
C43A8A2025D9D1D700591B77 /* brew.json in Resources */ = {isa = PBXBuildFile; fileRef = C43A8A1F25D9D1D700591B77 /* brew.json */; };
|
||||||
C43A8A2425D9D20D00591B77 /* BrewJsonParserTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C43A8A2325D9D20D00591B77 /* BrewJsonParserTest.swift */; };
|
C43A8A2425D9D20D00591B77 /* BrewJsonParserTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C43A8A2325D9D20D00591B77 /* BrewJsonParserTest.swift */; };
|
||||||
@@ -28,11 +35,16 @@
|
|||||||
C476FF9822B0DD830098105B /* Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = C476FF9722B0DD830098105B /* Alert.swift */; };
|
C476FF9822B0DD830098105B /* Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = C476FF9722B0DD830098105B /* Alert.swift */; };
|
||||||
C4811D2422D70A4700B5F6B3 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4811D2322D70A4700B5F6B3 /* App.swift */; };
|
C4811D2422D70A4700B5F6B3 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4811D2322D70A4700B5F6B3 /* App.swift */; };
|
||||||
C4811D2A22D70F9A00B5F6B3 /* MainMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4811D2922D70F9A00B5F6B3 /* MainMenu.swift */; };
|
C4811D2A22D70F9A00B5F6B3 /* MainMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4811D2922D70F9A00B5F6B3 /* MainMenu.swift */; };
|
||||||
|
C481F79726164A78004FBCFF /* PrefsVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5420395826135DC100FB00FA /* PrefsVC.swift */; };
|
||||||
|
C481F79A26164A7C004FBCFF /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5420395E2613607600FB00FA /* Preferences.swift */; };
|
||||||
C48D0C9025CC7FD000CC7490 /* StatsView.xib in Resources */ = {isa = PBXBuildFile; fileRef = C48D0C8F25CC7FD000CC7490 /* StatsView.xib */; };
|
C48D0C9025CC7FD000CC7490 /* StatsView.xib in Resources */ = {isa = PBXBuildFile; fileRef = C48D0C8F25CC7FD000CC7490 /* StatsView.xib */; };
|
||||||
C48D0C9325CC804200CC7490 /* XibLoadable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48D0C9225CC804200CC7490 /* XibLoadable.swift */; };
|
C48D0C9325CC804200CC7490 /* XibLoadable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48D0C9225CC804200CC7490 /* XibLoadable.swift */; };
|
||||||
C48D0C9625CC80B100CC7490 /* HeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48D0C9525CC80B100CC7490 /* HeaderView.swift */; };
|
C48D0C9625CC80B100CC7490 /* HeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48D0C9525CC80B100CC7490 /* HeaderView.swift */; };
|
||||||
C48D0C9A25CC888B00CC7490 /* HeaderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = C48D0C9925CC888B00CC7490 /* HeaderView.xib */; };
|
C48D0C9A25CC888B00CC7490 /* HeaderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = C48D0C9925CC888B00CC7490 /* HeaderView.xib */; };
|
||||||
C48D0CA325CC992000CC7490 /* StatsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48D0CA225CC992000CC7490 /* StatsView.swift */; };
|
C48D0CA325CC992000CC7490 /* StatsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48D0CA225CC992000CC7490 /* StatsView.swift */; };
|
||||||
|
C4998F0626175E7200B2526E /* HotKey in Frameworks */ = {isa = PBXBuildFile; productRef = C4998F0526175E7200B2526E /* HotKey */; };
|
||||||
|
C4998F0A2617633900B2526E /* PrefsWC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4998F092617633900B2526E /* PrefsWC.swift */; };
|
||||||
|
C4998F0B2617633900B2526E /* PrefsWC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4998F092617633900B2526E /* PrefsWC.swift */; };
|
||||||
C49EAB46259FC305007F6C3B /* Paths.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49EAB45259FC305007F6C3B /* Paths.swift */; };
|
C49EAB46259FC305007F6C3B /* Paths.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49EAB45259FC305007F6C3B /* Paths.swift */; };
|
||||||
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 */; };
|
||||||
@@ -62,6 +74,7 @@
|
|||||||
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 */; };
|
||||||
|
C4FBFC532616485F00CDB8E1 /* PhpVersionDetectionTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4FBFC512616485F00CDB8E1 /* PhpVersionDetectionTest.swift */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
@@ -75,6 +88,8 @@
|
|||||||
/* End PBXContainerItemProxy section */
|
/* End PBXContainerItemProxy section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
|
5420395826135DC100FB00FA /* PrefsVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrefsVC.swift; sourceTree = "<group>"; };
|
||||||
|
5420395E2613607600FB00FA /* Preferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = "<group>"; };
|
||||||
C405A4CE24B9B9130062FAFA /* InternetAccessPolicy.strings */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; path = InternetAccessPolicy.strings; sourceTree = "<group>"; };
|
C405A4CE24B9B9130062FAFA /* InternetAccessPolicy.strings */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; path = InternetAccessPolicy.strings; sourceTree = "<group>"; };
|
||||||
C405A4CF24B9B9140062FAFA /* InternetAccessPolicy.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = InternetAccessPolicy.plist; sourceTree = "<group>"; };
|
C405A4CF24B9B9140062FAFA /* InternetAccessPolicy.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = InternetAccessPolicy.plist; sourceTree = "<group>"; };
|
||||||
C412E5FB25700D5300A1FB67 /* HomebrewPackage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomebrewPackage.swift; sourceTree = "<group>"; };
|
C412E5FB25700D5300A1FB67 /* HomebrewPackage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomebrewPackage.swift; sourceTree = "<group>"; };
|
||||||
@@ -88,7 +103,10 @@
|
|||||||
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 /* PhpInstallation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpInstallation.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>"; };
|
||||||
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>"; };
|
||||||
|
C4232EE42612526500158FC6 /* Credits.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = Credits.html; sourceTree = "<group>"; };
|
||||||
|
C42759662627662800093CAE /* NSMenuExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSMenuExtension.swift; sourceTree = "<group>"; };
|
||||||
C43A8A1925D9CD1000591B77 /* Utility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utility.swift; sourceTree = "<group>"; };
|
C43A8A1925D9CD1000591B77 /* Utility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utility.swift; sourceTree = "<group>"; };
|
||||||
C43A8A1F25D9D1D700591B77 /* brew.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = brew.json; sourceTree = "<group>"; };
|
C43A8A1F25D9D1D700591B77 /* brew.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = brew.json; sourceTree = "<group>"; };
|
||||||
C43A8A2325D9D20D00591B77 /* BrewJsonParserTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrewJsonParserTest.swift; sourceTree = "<group>"; };
|
C43A8A2325D9D20D00591B77 /* BrewJsonParserTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrewJsonParserTest.swift; sourceTree = "<group>"; };
|
||||||
@@ -104,6 +122,7 @@
|
|||||||
C48D0C9525CC80B100CC7490 /* HeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeaderView.swift; sourceTree = "<group>"; };
|
C48D0C9525CC80B100CC7490 /* HeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeaderView.swift; sourceTree = "<group>"; };
|
||||||
C48D0C9925CC888B00CC7490 /* HeaderView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = HeaderView.xib; sourceTree = "<group>"; };
|
C48D0C9925CC888B00CC7490 /* HeaderView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = HeaderView.xib; sourceTree = "<group>"; };
|
||||||
C48D0CA225CC992000CC7490 /* StatsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsView.swift; sourceTree = "<group>"; };
|
C48D0CA225CC992000CC7490 /* StatsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsView.swift; sourceTree = "<group>"; };
|
||||||
|
C4998F092617633900B2526E /* PrefsWC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrefsWC.swift; sourceTree = "<group>"; };
|
||||||
C49EAB45259FC305007F6C3B /* Paths.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Paths.swift; sourceTree = "<group>"; };
|
C49EAB45259FC305007F6C3B /* Paths.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Paths.swift; sourceTree = "<group>"; };
|
||||||
C4ACA38E25C754C100060C66 /* PhpExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpExtension.swift; sourceTree = "<group>"; };
|
C4ACA38E25C754C100060C66 /* PhpExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpExtension.swift; sourceTree = "<group>"; };
|
||||||
C4D8016522B1584700C6DA1B /* Startup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Startup.swift; sourceTree = "<group>"; };
|
C4D8016522B1584700C6DA1B /* Startup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Startup.swift; sourceTree = "<group>"; };
|
||||||
@@ -118,6 +137,7 @@
|
|||||||
C4F780AD25D80B37000DBC97 /* ExtensionParserTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionParserTest.swift; sourceTree = "<group>"; };
|
C4F780AD25D80B37000DBC97 /* ExtensionParserTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionParserTest.swift; sourceTree = "<group>"; };
|
||||||
C4F8C0A322D4F12C002EFE61 /* DateExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateExtension.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>"; };
|
C4F8C0A522D4FA41002EFE61 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
|
||||||
|
C4FBFC512616485F00CDB8E1 /* PhpVersionDetectionTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhpVersionDetectionTest.swift; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
@@ -125,6 +145,7 @@
|
|||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
C4998F0626175E7200B2526E /* HotKey in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -138,6 +159,17 @@
|
|||||||
/* End PBXFrameworksBuildPhase section */
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXGroup section */
|
/* Begin PBXGroup section */
|
||||||
|
5420395726135DB800FB00FA /* Preferences */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
C4998F092617633900B2526E /* PrefsWC.swift */,
|
||||||
|
5420395826135DC100FB00FA /* PrefsVC.swift */,
|
||||||
|
5420395E2613607600FB00FA /* Preferences.swift */,
|
||||||
|
C41CD0272628D8E20065BBED /* Keybinds */,
|
||||||
|
);
|
||||||
|
path = Preferences;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
C405A4CD24B9B9070062FAFA /* IAP */ = {
|
C405A4CD24B9B9070062FAFA /* IAP */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -176,6 +208,7 @@
|
|||||||
C4EE188322D3386B00E126E5 /* Constants.swift */,
|
C4EE188322D3386B00E126E5 /* Constants.swift */,
|
||||||
C41E181722CB61EB0072CF09 /* Domain */,
|
C41E181722CB61EB0072CF09 /* Domain */,
|
||||||
C41C1B3F22B0098000E7CF16 /* Info.plist */,
|
C41C1B3F22B0098000E7CF16 /* Info.plist */,
|
||||||
|
C4232EE42612526500158FC6 /* Credits.html */,
|
||||||
C41C1B4022B0098000E7CF16 /* phpmon.entitlements */,
|
C41C1B4022B0098000E7CF16 /* phpmon.entitlements */,
|
||||||
C41C1B3A22B0098000E7CF16 /* Assets.xcassets */,
|
C41C1B3A22B0098000E7CF16 /* Assets.xcassets */,
|
||||||
C473319E2470923A009A0597 /* Localizable.strings */,
|
C473319E2470923A009A0597 /* Localizable.strings */,
|
||||||
@@ -184,9 +217,18 @@
|
|||||||
path = phpmon;
|
path = phpmon;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
C41CD0272628D8E20065BBED /* Keybinds */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
C41CD0282628D8EE0065BBED /* GlobalKeybindPreference.swift */,
|
||||||
|
);
|
||||||
|
path = Keybinds;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
C41E181722CB61EB0072CF09 /* Domain */ = {
|
C41E181722CB61EB0072CF09 /* Domain */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
5420395726135DB800FB00FA /* Preferences */,
|
||||||
C4F7808A25D7F918000DBC97 /* Terminal */,
|
C4F7808A25D7F918000DBC97 /* Terminal */,
|
||||||
C4B13B1D25C4915000548C3A /* Core */,
|
C4B13B1D25C4915000548C3A /* Core */,
|
||||||
C47331A0247093AC009A0597 /* Menu */,
|
C47331A0247093AC009A0597 /* Menu */,
|
||||||
@@ -240,6 +282,7 @@
|
|||||||
C4F7809B25D80344000DBC97 /* CommandTest.swift */,
|
C4F7809B25D80344000DBC97 /* CommandTest.swift */,
|
||||||
C4F780AD25D80B37000DBC97 /* ExtensionParserTest.swift */,
|
C4F780AD25D80B37000DBC97 /* ExtensionParserTest.swift */,
|
||||||
C43A8A2325D9D20D00591B77 /* BrewJsonParserTest.swift */,
|
C43A8A2325D9D20D00591B77 /* BrewJsonParserTest.swift */,
|
||||||
|
C4FBFC512616485F00CDB8E1 /* PhpVersionDetectionTest.swift */,
|
||||||
C43A8A1925D9CD1000591B77 /* Utility.swift */,
|
C43A8A1925D9CD1000591B77 /* Utility.swift */,
|
||||||
);
|
);
|
||||||
path = "phpmon-tests";
|
path = "phpmon-tests";
|
||||||
@@ -263,6 +306,7 @@
|
|||||||
C4F8C0A322D4F12C002EFE61 /* DateExtension.swift */,
|
C4F8C0A322D4F12C002EFE61 /* DateExtension.swift */,
|
||||||
C46FA23E246C358E00944F05 /* StringExtension.swift */,
|
C46FA23E246C358E00944F05 /* StringExtension.swift */,
|
||||||
C48D0C9225CC804200CC7490 /* XibLoadable.swift */,
|
C48D0C9225CC804200CC7490 /* XibLoadable.swift */,
|
||||||
|
C42759662627662800093CAE /* NSMenuExtension.swift */,
|
||||||
);
|
);
|
||||||
path = Extensions;
|
path = Extensions;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -283,6 +327,9 @@
|
|||||||
dependencies = (
|
dependencies = (
|
||||||
);
|
);
|
||||||
name = "PHP Monitor";
|
name = "PHP Monitor";
|
||||||
|
packageProductDependencies = (
|
||||||
|
C4998F0526175E7200B2526E /* HotKey */,
|
||||||
|
);
|
||||||
productName = phpmon;
|
productName = phpmon;
|
||||||
productReference = C41C1B3322B0097F00E7CF16 /* PHP Monitor.app */;
|
productReference = C41C1B3322B0097F00E7CF16 /* PHP Monitor.app */;
|
||||||
productType = "com.apple.product-type.application";
|
productType = "com.apple.product-type.application";
|
||||||
@@ -333,6 +380,9 @@
|
|||||||
Base,
|
Base,
|
||||||
);
|
);
|
||||||
mainGroup = C41C1B2A22B0097F00E7CF16;
|
mainGroup = C41C1B2A22B0097F00E7CF16;
|
||||||
|
packageReferences = (
|
||||||
|
C4998F0426175E7200B2526E /* XCRemoteSwiftPackageReference "HotKey" */,
|
||||||
|
);
|
||||||
productRefGroup = C41C1B3422B0097F00E7CF16 /* Products */;
|
productRefGroup = C41C1B3422B0097F00E7CF16 /* Products */;
|
||||||
projectDirPath = "";
|
projectDirPath = "";
|
||||||
projectRoot = "";
|
projectRoot = "";
|
||||||
@@ -352,6 +402,7 @@
|
|||||||
C41C1B3E22B0098000E7CF16 /* Main.storyboard in Resources */,
|
C41C1B3E22B0098000E7CF16 /* Main.storyboard in Resources */,
|
||||||
C48D0C9025CC7FD000CC7490 /* StatsView.xib in Resources */,
|
C48D0C9025CC7FD000CC7490 /* StatsView.xib in Resources */,
|
||||||
C405A4D124B9B9140062FAFA /* InternetAccessPolicy.plist in Resources */,
|
C405A4D124B9B9140062FAFA /* InternetAccessPolicy.plist in Resources */,
|
||||||
|
C4232EE52612526500158FC6 /* Credits.html in Resources */,
|
||||||
C473319F2470923A009A0597 /* Localizable.strings in Resources */,
|
C473319F2470923A009A0597 /* Localizable.strings in Resources */,
|
||||||
C405A4D024B9B9140062FAFA /* InternetAccessPolicy.strings in Resources */,
|
C405A4D024B9B9140062FAFA /* InternetAccessPolicy.strings in Resources */,
|
||||||
C48D0C9A25CC888B00CC7490 /* HeaderView.xib in Resources */,
|
C48D0C9A25CC888B00CC7490 /* HeaderView.xib in Resources */,
|
||||||
@@ -376,17 +427,22 @@
|
|||||||
files = (
|
files = (
|
||||||
C4ACA38F25C754C100060C66 /* PhpExtension.swift in Sources */,
|
C4ACA38F25C754C100060C66 /* PhpExtension.swift in Sources */,
|
||||||
C4D8016622B1584700C6DA1B /* Startup.swift in Sources */,
|
C4D8016622B1584700C6DA1B /* Startup.swift in Sources */,
|
||||||
|
C4998F0A2617633900B2526E /* PrefsWC.swift in Sources */,
|
||||||
C4F8C0A422D4F12C002EFE61 /* DateExtension.swift in Sources */,
|
C4F8C0A422D4F12C002EFE61 /* DateExtension.swift in Sources */,
|
||||||
|
5420395926135DC100FB00FA /* PrefsVC.swift in Sources */,
|
||||||
C41C1B4722B009A400E7CF16 /* Shell.swift in Sources */,
|
C41C1B4722B009A400E7CF16 /* Shell.swift in Sources */,
|
||||||
C41C1B4D22B0215A00E7CF16 /* Actions.swift in Sources */,
|
C41C1B4D22B0215A00E7CF16 /* Actions.swift in Sources */,
|
||||||
C48D0CA325CC992000CC7490 /* StatsView.swift in Sources */,
|
C48D0CA325CC992000CC7490 /* StatsView.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 */,
|
||||||
C41C1B4922B00A9800E7CF16 /* MenuBarImageGenerator.swift in Sources */,
|
C41C1B4922B00A9800E7CF16 /* MenuBarImageGenerator.swift in Sources */,
|
||||||
|
5420395F2613607600FB00FA /* Preferences.swift in Sources */,
|
||||||
C48D0C9325CC804200CC7490 /* XibLoadable.swift in Sources */,
|
C48D0C9325CC804200CC7490 /* XibLoadable.swift in Sources */,
|
||||||
C4811D2A22D70F9A00B5F6B3 /* MainMenu.swift in Sources */,
|
C4811D2A22D70F9A00B5F6B3 /* MainMenu.swift in Sources */,
|
||||||
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 */,
|
||||||
C41C1B4B22B019FF00E7CF16 /* PhpInstallation.swift in Sources */,
|
C41C1B4B22B019FF00E7CF16 /* PhpInstallation.swift in Sources */,
|
||||||
C49EAB46259FC305007F6C3B /* Paths.swift in Sources */,
|
C49EAB46259FC305007F6C3B /* Paths.swift in Sources */,
|
||||||
C476FF9822B0DD830098105B /* Alert.swift in Sources */,
|
C476FF9822B0DD830098105B /* Alert.swift in Sources */,
|
||||||
@@ -402,19 +458,24 @@
|
|||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
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 /* PhpInstallation.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 */,
|
||||||
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 */,
|
||||||
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 */,
|
||||||
C4F780CD25D80B75000DBC97 /* Alert.swift in Sources */,
|
C4F780CD25D80B75000DBC97 /* Alert.swift in Sources */,
|
||||||
|
C481F79726164A78004FBCFF /* PrefsVC.swift in Sources */,
|
||||||
C4F7809C25D80344000DBC97 /* CommandTest.swift in Sources */,
|
C4F7809C25D80344000DBC97 /* CommandTest.swift in Sources */,
|
||||||
C4F780BA25D80B62000DBC97 /* AppDelegate.swift in Sources */,
|
C4F780BA25D80B62000DBC97 /* AppDelegate.swift in Sources */,
|
||||||
|
C4998F0B2617633900B2526E /* PrefsWC.swift in Sources */,
|
||||||
C4F780A225D804AA000DBC97 /* Paths.swift in Sources */,
|
C4F780A225D804AA000DBC97 /* Paths.swift in Sources */,
|
||||||
C4F780BD25D80B65000DBC97 /* Constants.swift in Sources */,
|
C4F780BD25D80B65000DBC97 /* Constants.swift in Sources */,
|
||||||
C4F780C325D80B75000DBC97 /* HeaderView.swift in Sources */,
|
C4F780C325D80B75000DBC97 /* HeaderView.swift in Sources */,
|
||||||
@@ -422,6 +483,7 @@
|
|||||||
C4F780C525D80B75000DBC97 /* MenuBarImageGenerator.swift in Sources */,
|
C4F780C525D80B75000DBC97 /* MenuBarImageGenerator.swift in Sources */,
|
||||||
C4F780B725D80B5D000DBC97 /* App.swift in Sources */,
|
C4F780B725D80B5D000DBC97 /* App.swift in Sources */,
|
||||||
C4F780C925D80B75000DBC97 /* StringExtension.swift in Sources */,
|
C4F780C925D80B75000DBC97 /* StringExtension.swift in Sources */,
|
||||||
|
C481F79A26164A7C004FBCFF /* Preferences.swift in Sources */,
|
||||||
C4F780CB25D80B75000DBC97 /* StatsView.swift in Sources */,
|
C4F780CB25D80B75000DBC97 /* StatsView.swift in Sources */,
|
||||||
C43A8A1A25D9CD1000591B77 /* Utility.swift in Sources */,
|
C43A8A1A25D9CD1000591B77 /* Utility.swift in Sources */,
|
||||||
C4F780C625D80B75000DBC97 /* XibLoadable.swift in Sources */,
|
C4F780C625D80B75000DBC97 /* XibLoadable.swift in Sources */,
|
||||||
@@ -577,7 +639,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 = 48;
|
CURRENT_PROJECT_VERSION = 56;
|
||||||
DEVELOPMENT_TEAM = 8M54J5J787;
|
DEVELOPMENT_TEAM = 8M54J5J787;
|
||||||
ENABLE_HARDENED_RUNTIME = YES;
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
INFOPLIST_FILE = phpmon/Info.plist;
|
INFOPLIST_FILE = phpmon/Info.plist;
|
||||||
@@ -585,7 +647,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/../Frameworks",
|
"@executable_path/../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 3.1;
|
MARKETING_VERSION = 3.4;
|
||||||
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 = "";
|
||||||
@@ -601,7 +663,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 = 48;
|
CURRENT_PROJECT_VERSION = 56;
|
||||||
DEVELOPMENT_TEAM = 8M54J5J787;
|
DEVELOPMENT_TEAM = 8M54J5J787;
|
||||||
ENABLE_HARDENED_RUNTIME = YES;
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
INFOPLIST_FILE = phpmon/Info.plist;
|
INFOPLIST_FILE = phpmon/Info.plist;
|
||||||
@@ -609,7 +671,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/../Frameworks",
|
"@executable_path/../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 3.1;
|
MARKETING_VERSION = 3.4;
|
||||||
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 = "";
|
||||||
@@ -690,6 +752,25 @@
|
|||||||
defaultConfigurationName = Release;
|
defaultConfigurationName = Release;
|
||||||
};
|
};
|
||||||
/* End XCConfigurationList section */
|
/* End XCConfigurationList section */
|
||||||
|
|
||||||
|
/* Begin XCRemoteSwiftPackageReference section */
|
||||||
|
C4998F0426175E7200B2526E /* XCRemoteSwiftPackageReference "HotKey" */ = {
|
||||||
|
isa = XCRemoteSwiftPackageReference;
|
||||||
|
repositoryURL = "https://github.com/soffes/HotKey";
|
||||||
|
requirement = {
|
||||||
|
kind = upToNextMinorVersion;
|
||||||
|
minimumVersion = 0.1.3;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/* End XCRemoteSwiftPackageReference section */
|
||||||
|
|
||||||
|
/* Begin XCSwiftPackageProductDependency section */
|
||||||
|
C4998F0526175E7200B2526E /* HotKey */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
package = C4998F0426175E7200B2526E /* XCRemoteSwiftPackageReference "HotKey" */;
|
||||||
|
productName = HotKey;
|
||||||
|
};
|
||||||
|
/* End XCSwiftPackageProductDependency section */
|
||||||
};
|
};
|
||||||
rootObject = C41C1B2B22B0097F00E7CF16 /* Project object */;
|
rootObject = C41C1B2B22B0097F00E7CF16 /* Project object */;
|
||||||
}
|
}
|
||||||
|
108
README.md
@@ -1,18 +1,21 @@
|
|||||||
# PHP Monitor
|
# PHP Monitor
|
||||||
|
|
||||||
|
> If this software has been useful to you, all I ask is that you **please star the repository**, so I know that the software is being used.
|
||||||
|
> You can also send me [feedback](https://twitter.com/nicoverbruggen) if the app came in handy.<br>**Thank you!** ⭐️
|
||||||
|
|
||||||
<img src="./phpmon/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png" alt="phpmon icon" width="128px" />
|
<img src="./phpmon/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png" alt="phpmon icon" width="128px" />
|
||||||
|
|
||||||
PHP Monitor (or phpmon) is a lightweight macOS utility app that runs on your Mac and displays the active PHP version in your status bar.
|
**PHP Monitor** (or phpmon) is a lightweight macOS utility app that runs on your Mac and displays the active PHP version in your status bar. It's tightly integrated with [Laravel Valet](https://github.com/laravel/valet), so you need to have it set up before you can use this.
|
||||||
|
|
||||||
<img src="./docs/screenshot.png" width="389px" alt="phpmon screenshot (menu bar app)"/>
|
<img src="./docs/screenshot34.png" width="412px" alt="phpmon screenshot (menu bar app)"/>
|
||||||
|
|
||||||
<small><i>Screenshot: A menu showing all of the functionality of PHP Monitor.</i></small>
|
<small><i>Screenshot: A menu showing all of the functionality of PHP Monitor.</i></small>
|
||||||
|
|
||||||
It's also super convenient to switch between different versions of PHP. You'll even get notifications (only if you choose to opt-in, of course)!
|
It's super convenient to switch between different versions of PHP. You'll even get notifications (only if you choose to opt-in, of course)!
|
||||||
|
|
||||||
<img src="./docs/notification.png" width="370px" alt="phpmon screenshot (notification)"/>
|
<img src="./docs/notification.png" width="370px" alt="phpmon screenshot (notification)"/>
|
||||||
|
|
||||||
It also gives you quick access to various useful functionality (like accessing configuration files, restarting services, and more).
|
PHP Monitor also gives you quick access to various useful functionality (like accessing configuration files, restarting services, and more).
|
||||||
|
|
||||||
## 🖥 System requirements
|
## 🖥 System requirements
|
||||||
|
|
||||||
@@ -40,10 +43,6 @@ To upgrade your existing installation, run:
|
|||||||
|
|
||||||
_The app is signed and notarized, meaning all you have to do is approve its first launch._
|
_The app is signed and notarized, meaning all you have to do is approve its first launch._
|
||||||
|
|
||||||
## ⭐️ Star me!
|
|
||||||
|
|
||||||
If this software has been useful to you, all I ask is that you **please star the repository**, so I know that the software is being used. You can also send me [feedback](https://twitter.com/nicoverbruggen) if the app came in handy. 😃
|
|
||||||
|
|
||||||
## 👨💻 Why build this?
|
## 👨💻 Why build 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.
|
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.
|
||||||
@@ -60,7 +59,25 @@ PHP Monitor performs some integrity checks to ensure a good experience when usin
|
|||||||
|
|
||||||
> If you are having issues, the first thing you should be doing is installing the latest version of PHP Monitor _and_ Laravel Valet. This can resolve a variety of issues. To upgrade Valet, run `composer global update`. Don't forget to run `valet install` after upgrading.
|
> If you are having issues, the first thing you should be doing is installing the latest version of PHP Monitor _and_ Laravel Valet. This can resolve a variety of issues. To upgrade Valet, run `composer global update`. Don't forget to run `valet install` after upgrading.
|
||||||
|
|
||||||
If you're still having issues, here's a few common issues and solutions:
|
If you're still having issues, here's a few common questions & answers, as well as issues and solutions:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><strong>Which versions of PHP are supported?</strong></summary>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>PHP 5.6</li>
|
||||||
|
<li>PHP 7.0</li>
|
||||||
|
<li>PHP 7.1</li>
|
||||||
|
<li>PHP 7.2</li>
|
||||||
|
<li>PHP 7.3</li>
|
||||||
|
<li>PHP 7.4</li>
|
||||||
|
<li>PHP 8.0</li>
|
||||||
|
<li>PHP 8.1</li>
|
||||||
|
</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.
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary><strong>I want PHP Monitor to start up when I boot my Mac!</strong></summary>
|
<summary><strong>I want PHP Monitor to start up when I boot my Mac!</strong></summary>
|
||||||
@@ -137,6 +154,35 @@ This problem is usually resolved by upgrading Valet and running `valet install`
|
|||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><strong>PHP Monitor tells me my installation is broken, but I don't see why!</strong></summary>
|
||||||
|
|
||||||
|
PHP Monitor tells you that a PHP installation is broken, if the configuration is causing warnings or errors when determining the version number.
|
||||||
|
|
||||||
|
Since PHP Monitor changes the linked version via Homebrew, both Valet *and* your terminal (CLI) should use the new PHP version.
|
||||||
|
|
||||||
|
However, this might not be the case on your system. You _might_ have a specific version of PHP linked if that is not the case. In that case, you may need to change your `.bashrc` or `.zshrc` file where the PATH is set (depending on the terminal you use).
|
||||||
|
|
||||||
|
You can find out which version of PHP is being used by running `which php`.
|
||||||
|
|
||||||
|
You can find out what exactly is causing the issue by running a command. On Intel, you can run (replace `7.4` with the version that is broken):
|
||||||
|
|
||||||
|
```
|
||||||
|
/usr/local/opt/php@7.4/bin/php -r "print phpversion();"
|
||||||
|
```
|
||||||
|
|
||||||
|
On Apple Silicon, you can run (replace `7.4` with the version that is broken):
|
||||||
|
|
||||||
|
```
|
||||||
|
/opt/homebrew/opt/php@7.4/bin/php -r "print phpversion();"
|
||||||
|
```
|
||||||
|
|
||||||
|
You should see an error or a warning here in the output.
|
||||||
|
|
||||||
|
Usually this is a duplicate extension declaration causing issues, or an extension that couldn't be loaded. You'll have to solve that issue yourself (usually by removing the offending extension or reinstalling).
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary><strong>One of the limits (memory limit, max POST size, max upload size) shows an exclamation mark!</strong></summary>
|
<summary><strong>One of the limits (memory limit, max POST size, max upload size) shows an exclamation mark!</strong></summary>
|
||||||
|
|
||||||
@@ -151,7 +197,7 @@ You must a provide a value like so: `1024K`, `256M`, `1G`. Alternatively, `-1` i
|
|||||||
<details>
|
<details>
|
||||||
<summary><strong>One of my commented out extensions is not being detected...</strong></summary>
|
<summary><strong>One of my commented out extensions is not being detected...</strong></summary>
|
||||||
|
|
||||||
The app searches in the relevant `php.ini` file for a specific pattern. For regular extensions:
|
The app searches in the relevant `.ini` files for a specific pattern. For regular extensions:
|
||||||
|
|
||||||
* `extension="*.so"`
|
* `extension="*.so"`
|
||||||
* `; extension="*.so"`
|
* `; extension="*.so"`
|
||||||
@@ -162,6 +208,9 @@ For Zend extensions:
|
|||||||
* `; zend_extension="*.so"`
|
* `; zend_extension="*.so"`
|
||||||
|
|
||||||
The `*` is a wildcard and the name of the extension. If you've commented out the extension, make sure you've commented it out with a semicolon (;) and a single space after the semicolon for PHP Monitor to detect it.
|
The `*` is a wildcard and the name of the extension. If you've commented out the extension, make sure you've commented it out with a semicolon (;) and a single space after the semicolon for PHP Monitor to detect it.
|
||||||
|
|
||||||
|
Since v3.4 all of the loaded .ini files are sourced to determine which extensions are enabled.
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
@@ -185,23 +234,47 @@ PHP Monitor itself doesn't do any network requests. Feel free to check the sourc
|
|||||||
|
|
||||||
This is a security feature of Brew. When you start a service as an administrator, the root user becomes the owner of relevant binaries.
|
This is a security feature of Brew. When you start a service as an administrator, the root user becomes the owner of relevant binaries.
|
||||||
|
|
||||||
You will need to manually clean up those folders yourself using `rm -rf` or by manually removing those folders via Finder.
|
You will need to manually clean up those folders yourself using `rm -rf` (or by manually removing those folders via Finder).
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## 📝 Another issue?
|
<details>
|
||||||
|
<summary><strong>The app has crashed!</strong></summary>
|
||||||
|
|
||||||
|
Please get in touch and open an issue. PHP Monitor shouldn't crash :)
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
## 📝 Having another issue?
|
||||||
|
|
||||||
I did not include any tracking or analytics software, so if you encounter issues, let me know [via an issue](https://github.com/nicoverbruggen/phpmon/issues/new).
|
I did not include any tracking or analytics software, so if you encounter issues, let me know [via an issue](https://github.com/nicoverbruggen/phpmon/issues/new).
|
||||||
|
|
||||||
## 💵 Support me?
|
## 💵 Support me?
|
||||||
|
|
||||||
I usually develop this application in my spare time, after work. If you find the application useful and you have a bit of money to spare, feel free to send me [a tip via PayPal](https://paypal.me/nicoverbruggen).
|
PHP Monitor is available entirely **free of charge**, but if you can afford it a donation helps keep the project alive and the app maintained.
|
||||||
|
|
||||||
|
You can find a [sponsor](https://nicoverbruggen.be/sponsor) link at the top of this repo or you could click the link here to be taken to my sponsorship page.
|
||||||
|
|
||||||
|
Donations really help with the Apple Developer Program cost, and keep me motivated to keep working on PHP Monitor outside of work hours (I do have a day job!).
|
||||||
|
|
||||||
|
## 😎 Acknowledgements
|
||||||
|
|
||||||
|
While I did make this application during my own free time, I have been lucky enough to do various experiments during work hours at [DIVE](https://dive.be). I'd also like to shout out the following folks:
|
||||||
|
|
||||||
|
* My colleagues at [DIVE](https://dive.be)
|
||||||
|
* The [Homebrew](https://brew.sh/) team who maintain
|
||||||
|
* The [developers & maintainers of Valet](https://github.com/laravel/valet/graphs/contributors)
|
||||||
|
* Everyone in the Laravel community who shared the app (thanks!)
|
||||||
|
* Various folks who [reached](https://twitter.com/stauffermatt) [out](https://twitter.com/marcelpociot)
|
||||||
|
* Everyone who left feedback via issues
|
||||||
|
|
||||||
|
Thank you very much for your contributions, kind words and support.
|
||||||
|
|
||||||
## 🚜 How it works
|
## 🚜 How it works
|
||||||
|
|
||||||
### Loading info about PHP in the background
|
### Loading info about PHP in the background
|
||||||
|
|
||||||
This utility runs `php -r 'print phpversion()'` 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.
|
||||||
|
|
||||||
@@ -217,11 +290,8 @@ This means:
|
|||||||
|
|
||||||
The utility runs the following commands:
|
The utility runs the following commands:
|
||||||
|
|
||||||
- Unlink all detected PHP versions
|
- Unlink all detected PHP versions & stop the respective `php@X.X` services
|
||||||
- Switch to whatever version of PHP `php` is at (this is done to ensure that Valet works, even when attempting to use PHP 5.6)
|
- Link the desired version of PHP, and start the associated service
|
||||||
- Stop all relevant services (`php`, `nginx`)
|
|
||||||
- Link the desired version of PHP
|
|
||||||
- Start the correct `php` service for the desired PHP version
|
|
||||||
|
|
||||||
### Want to know more?
|
### Want to know more?
|
||||||
|
|
||||||
|
@@ -6,7 +6,7 @@ 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 |
|
||||||
| ------- | ------------- | ------------------ | ----- | ----- |
|
| ------- | ------------- | ------------------ | ----- | ----- |
|
||||||
| 3.0 | ✅ Universal binary | ✅ | Big Sur (11.0) | macOS 10.14+ |
|
| 3.x | ✅ Universal binary | ✅ | Big Sur (11.0) | macOS 10.14+ |
|
||||||
| 2.6 | ✅ Universal binary | ❌ | Big Sur (11.0) | macOS 10.14+ |
|
| 2.6 | ✅ Universal binary | ❌ | Big Sur (11.0) | macOS 10.14+ |
|
||||||
| 2.5 | ✴️ Universal binary<br/>`/usr/local/homebrew` installations only | ❌ | Big Sur (11.0)<br/>Catalina (10.15) | macOS 10.14+ |
|
| 2.5 | ✴️ Universal binary<br/>`/usr/local/homebrew` installations only | ❌ | Big Sur (11.0)<br/>Catalina (10.15) | macOS 10.14+ |
|
||||||
| 2.4 | ✴️ Universal binary<br/>`/usr/local/homebrew` installations only | ❌ | Big Sur (11.0)<br/>Catalina (10.15) | macOS 10.14+ |
|
| 2.4 | ✴️ Universal binary<br/>`/usr/local/homebrew` installations only | ❌ | Big Sur (11.0)<br/>Catalina (10.15) | macOS 10.14+ |
|
||||||
|
Before Width: | Height: | Size: 127 KiB |
BIN
docs/screenshot34.png
Normal file
After Width: | Height: | Size: 130 KiB |
@@ -23,24 +23,35 @@ class ExtensionParserTest: XCTestCase {
|
|||||||
func testExtensionNameIsCorrect() throws {
|
func testExtensionNameIsCorrect() throws {
|
||||||
let extensions = PhpExtension.load(from: Self.phpIniFileUrl)
|
let extensions = PhpExtension.load(from: Self.phpIniFileUrl)
|
||||||
|
|
||||||
XCTAssertEqual(extensions.first!.name, "xdebug")
|
let extensionNames = extensions.map { (ext) -> String in
|
||||||
XCTAssertEqual(extensions.last!.name, "imagick")
|
return ext.name
|
||||||
|
}
|
||||||
|
|
||||||
|
XCTAssertTrue(extensionNames.contains("xdebug"))
|
||||||
|
XCTAssertTrue(extensionNames.contains("imagick"))
|
||||||
|
XCTAssertTrue(extensionNames.contains("opcache"))
|
||||||
|
XCTAssertTrue(extensionNames.contains("yaml"))
|
||||||
|
XCTAssertFalse(extensionNames.contains("fake"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func testExtensionStatusIsCorrect() throws {
|
func testExtensionStatusIsCorrect() throws {
|
||||||
let extensions = PhpExtension.load(from: Self.phpIniFileUrl)
|
let extensions = PhpExtension.load(from: Self.phpIniFileUrl)
|
||||||
|
|
||||||
XCTAssertEqual(extensions.first!.enabled, true)
|
// xdebug should be enabled
|
||||||
XCTAssertEqual(extensions.last!.enabled, false)
|
XCTAssertEqual(extensions[0].enabled, true)
|
||||||
|
|
||||||
|
// imagick should be disabled
|
||||||
|
XCTAssertEqual(extensions[1].enabled, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
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, 2)
|
XCTAssertEqual(extensions.count, 4)
|
||||||
|
|
||||||
// Try to disable it!
|
// Try to disable xdebug (should be detected first)!
|
||||||
let xdebug = extensions.first!
|
let xdebug = extensions.first!
|
||||||
|
XCTAssertTrue(xdebug.name == "xdebug")
|
||||||
XCTAssertEqual(xdebug.enabled, true)
|
XCTAssertEqual(xdebug.enabled, true)
|
||||||
xdebug.toggle()
|
xdebug.toggle()
|
||||||
XCTAssertEqual(xdebug.enabled, false)
|
XCTAssertEqual(xdebug.enabled, false)
|
||||||
|
30
phpmon-tests/PhpVersionDetectionTest.swift
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
//
|
||||||
|
// PhpVersionDetectionTest.swift
|
||||||
|
// phpmon-tests
|
||||||
|
//
|
||||||
|
// Created by Nico Verbruggen on 01/04/2021.
|
||||||
|
// Copyright © 2021 Nico Verbruggen. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import XCTest
|
||||||
|
|
||||||
|
class PhpVersionDetectionTest: XCTestCase {
|
||||||
|
|
||||||
|
func testCanDetectValidPhpVersions() throws {
|
||||||
|
let outcome = Actions.extractPhpVersions(from: [
|
||||||
|
"", // empty lines should be omitted
|
||||||
|
"php@8.0",
|
||||||
|
"php@8.0", // should only be detected once
|
||||||
|
"meta-php@8.0", // should be omitted, invalid
|
||||||
|
"php@8.0-coolio", // should be omitted, invalid
|
||||||
|
"php@7.0",
|
||||||
|
"",
|
||||||
|
"unrelatedphp@1.0", // should be omitted, invalid
|
||||||
|
"php@5.6",
|
||||||
|
"php@5.4" // should be omitted, not supported
|
||||||
|
], checkBinaries: false)
|
||||||
|
|
||||||
|
XCTAssertEqual(outcome, ["8.0", "7.0", "5.6"])
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -1,5 +1,8 @@
|
|||||||
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/yaml.so"
|
||||||
|
#zend_extension="/opt/homebrew/opt/php/lib/php/20200930/fake.so"
|
||||||
|
|
||||||
[PHP]
|
[PHP]
|
||||||
|
|
||||||
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 7.7 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 714 B After Width: | Height: | Size: 558 B |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 315 KiB After Width: | Height: | Size: 148 KiB |
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"info" : {
|
"info" : {
|
||||||
"version" : 1,
|
"author" : "xcode",
|
||||||
"author" : "xcode"
|
"version" : 1
|
||||||
}
|
}
|
||||||
}
|
}
|
Before Width: | Height: | Size: 386 B After Width: | Height: | Size: 278 B |
Before Width: | Height: | Size: 780 B After Width: | Height: | Size: 500 B |
22
phpmon/Assets.xcassets/StatusBarIconStatic.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "phpmon.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "phpmon@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
phpmon/Assets.xcassets/StatusBarIconStatic.imageset/phpmon.png
vendored
Normal file
After Width: | Height: | Size: 229 B |
BIN
phpmon/Assets.xcassets/StatusBarIconStatic.imageset/phpmon@2x.png
vendored
Normal file
After Width: | Height: | Size: 358 B |
22
phpmon/Credits.html
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
background-color: #FFF;
|
||||||
|
color: #000;
|
||||||
|
font-family: -apple-system;
|
||||||
|
font-size: 11px;
|
||||||
|
padding: 5px;
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<br>
|
||||||
|
<p><b>Want to spread the love?</b> Leave a <a href="https://github.com/nicoverbruggen/phpmon">star on GitHub</a>!</p>
|
||||||
|
<p><b>Having issues?</b> Consult the <a href="https://github.com/nicoverbruggen/phpmon#%EF%B8%8F-faq--troubleshooting">FAQ & Troubleshooting</a> section.</p>
|
||||||
|
<p><b>Want to support me?</b> You can <a href="https://nicoverbruggen.be/sponsor">financially support</a> the continued development of this app.</p>
|
||||||
|
<br>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@@ -4,12 +4,18 @@
|
|||||||
//
|
//
|
||||||
// Copyright © 2021 Nico Verbruggen. All rights reserved.
|
// Copyright © 2021 Nico Verbruggen. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
import Cocoa
|
import Cocoa
|
||||||
|
import HotKey
|
||||||
|
|
||||||
class App {
|
class App {
|
||||||
|
|
||||||
static let shared = App()
|
static let shared = App()
|
||||||
|
|
||||||
|
init() {
|
||||||
|
loadGlobalHotkey()
|
||||||
|
}
|
||||||
|
|
||||||
static var phpInstall: PhpInstallation? {
|
static var phpInstall: PhpInstallation? {
|
||||||
return App.shared.currentInstall
|
return App.shared.currentInstall
|
||||||
}
|
}
|
||||||
@@ -18,6 +24,14 @@ class App {
|
|||||||
return App.shared.busy
|
return App.shared.busy
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** The list of preferences that are currently active. */
|
||||||
|
var preferences: [PreferenceName: Bool]!
|
||||||
|
|
||||||
|
/**
|
||||||
|
The window controller of the currently active window.
|
||||||
|
*/
|
||||||
|
var windowController: NSWindowController? = nil
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Whether the application is busy switching versions.
|
Whether the application is busy switching versions.
|
||||||
*/
|
*/
|
||||||
@@ -43,7 +57,7 @@ class App {
|
|||||||
*/
|
*/
|
||||||
var brewPhpPackage: HomebrewPackage? = nil {
|
var brewPhpPackage: HomebrewPackage? = nil {
|
||||||
didSet {
|
didSet {
|
||||||
self.brewPhpVersion = self.brewPhpPackage!.version
|
brewPhpVersion = brewPhpPackage!.version
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,4 +72,43 @@ class App {
|
|||||||
*/
|
*/
|
||||||
var brewPhpVersion: String = "8.0"
|
var brewPhpVersion: String = "8.0"
|
||||||
|
|
||||||
|
/**
|
||||||
|
The shortcut the user has requested.
|
||||||
|
*/
|
||||||
|
var shortcutHotkey: HotKey? = nil {
|
||||||
|
didSet {
|
||||||
|
self.setupGlobalHotkeyListener()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Methods
|
||||||
|
|
||||||
|
private func loadGlobalHotkey() {
|
||||||
|
let hotkey = Preferences.preferences[.globalHotkey] as! String?
|
||||||
|
if hotkey == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let keybindPref = GlobalKeybindPreference.fromJson(hotkey!)
|
||||||
|
|
||||||
|
if (keybindPref != nil) {
|
||||||
|
self.shortcutHotkey = HotKey(keyCombo: KeyCombo(
|
||||||
|
carbonKeyCode: keybindPref!.keyCode,
|
||||||
|
carbonModifiers: keybindPref!.carbonFlags
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
self.shortcutHotkey = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setupGlobalHotkeyListener() {
|
||||||
|
guard let hotKey = self.shortcutHotkey else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
hotKey.keyDownHandler = {
|
||||||
|
MainMenu.shared.statusItem.button?.performClick(nil)
|
||||||
|
NSApplication.shared.activate(ignoringOtherApps: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,8 +1,9 @@
|
|||||||
<?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="17506" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="17701" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<deployment identifier="macosx"/>
|
<deployment identifier="macosx"/>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="17506"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="17701"/>
|
||||||
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<scenes>
|
<scenes>
|
||||||
<!--Application-->
|
<!--Application-->
|
||||||
@@ -54,5 +55,172 @@
|
|||||||
</objects>
|
</objects>
|
||||||
<point key="canvasLocation" x="-343" y="-16"/>
|
<point key="canvasLocation" x="-343" y="-16"/>
|
||||||
</scene>
|
</scene>
|
||||||
|
<!--Window Controller-->
|
||||||
|
<scene sceneID="PQa-AT-b2a">
|
||||||
|
<objects>
|
||||||
|
<windowController storyboardIdentifier="preferencesWindow" showSeguePresentationStyle="single" id="hLJ-Fd-wRr" customClass="PrefsWC" customModule="PHP_Monitor" customModuleProvider="target" sceneMemberID="viewController">
|
||||||
|
<window key="window" title="Window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="h4c-3b-nko">
|
||||||
|
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
|
||||||
|
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
||||||
|
<rect key="contentRect" x="372" y="403" width="480" height="270"/>
|
||||||
|
<rect key="screenRect" x="0.0" y="0.0" width="2304" height="1271"/>
|
||||||
|
<view key="contentView" id="2yL-50-11x">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="480" height="270"/>
|
||||||
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
</view>
|
||||||
|
<connections>
|
||||||
|
<outlet property="delegate" destination="hLJ-Fd-wRr" id="6HE-8Y-aCO"/>
|
||||||
|
</connections>
|
||||||
|
</window>
|
||||||
|
<connections>
|
||||||
|
<segue destination="AW2-rV-rbS" kind="relationship" relationship="window.shadowedContentViewController" id="3dX-9V-eA0"/>
|
||||||
|
</connections>
|
||||||
|
</windowController>
|
||||||
|
<customObject id="OF0-qs-3Oh" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||||
|
</objects>
|
||||||
|
<point key="canvasLocation" x="-374" y="327"/>
|
||||||
|
</scene>
|
||||||
|
<!--Preferences-->
|
||||||
|
<scene sceneID="iyi-IS-7Ps">
|
||||||
|
<objects>
|
||||||
|
<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">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="574" height="189"/>
|
||||||
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
<subviews>
|
||||||
|
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="GSr-K5-3yw">
|
||||||
|
<rect key="frame" x="485" y="13" width="76" height="32"/>
|
||||||
|
<buttonCell key="cell" type="push" title="CLOSE" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="ocw-Rx-gyh">
|
||||||
|
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||||
|
<font key="font" metaFont="system"/>
|
||||||
|
<string key="keyEquivalent" base64-UTF8="YES">
|
||||||
|
Gw
|
||||||
|
</string>
|
||||||
|
</buttonCell>
|
||||||
|
<connections>
|
||||||
|
<action selector="pressed:" target="AW2-rV-rbS" id="8dA-y4-voq"/>
|
||||||
|
</connections>
|
||||||
|
</button>
|
||||||
|
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="MEf-MN-oXt">
|
||||||
|
<rect key="frame" x="148" y="152" width="406" height="18"/>
|
||||||
|
<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"/>
|
||||||
|
<font key="font" metaFont="system"/>
|
||||||
|
</buttonCell>
|
||||||
|
<connections>
|
||||||
|
<action selector="toggledDynamicIcon:" target="AW2-rV-rbS" id="cuJ-mt-agf"/>
|
||||||
|
</connections>
|
||||||
|
</button>
|
||||||
|
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="JrH-aa-AzL">
|
||||||
|
<rect key="frame" x="148" y="131" width="408" height="14"/>
|
||||||
|
<textFieldCell key="cell" title="DYN_ICON_DESC" id="MHA-Xt-xgF">
|
||||||
|
<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>
|
||||||
|
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="V7b-jv-oCB">
|
||||||
|
<rect key="frame" x="143" y="75" width="184" height="32"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstAttribute="width" constant="170" id="9jD-Bf-T2M"/>
|
||||||
|
</constraints>
|
||||||
|
<backgroundFilters>
|
||||||
|
<ciFilter name="CIDotScreen">
|
||||||
|
<configuration>
|
||||||
|
<real key="inputAngle" value="0.0"/>
|
||||||
|
<ciVector key="inputCenter">
|
||||||
|
<real value="150"/>
|
||||||
|
<real value="150"/>
|
||||||
|
</ciVector>
|
||||||
|
<null key="inputImage"/>
|
||||||
|
<real key="inputSharpness" value="0.69999999999999996"/>
|
||||||
|
<real key="inputWidth" value="6"/>
|
||||||
|
</configuration>
|
||||||
|
</ciFilter>
|
||||||
|
</backgroundFilters>
|
||||||
|
<buttonCell key="cell" type="push" title="SET_SHORTCUT" bezelStyle="rounded" alignment="center" borderStyle="border" inset="2" id="R63-tN-KVQ">
|
||||||
|
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||||
|
<font key="font" metaFont="system"/>
|
||||||
|
</buttonCell>
|
||||||
|
<connections>
|
||||||
|
<action selector="register:" target="AW2-rV-rbS" id="4Mj-eM-4eW"/>
|
||||||
|
</connections>
|
||||||
|
</button>
|
||||||
|
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="YsQ-AZ-Aei">
|
||||||
|
<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">
|
||||||
|
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||||
|
<font key="font" metaFont="system" size="11"/>
|
||||||
|
</buttonCell>
|
||||||
|
<connections>
|
||||||
|
<action selector="unregister:" target="AW2-rV-rbS" id="2RI-4w-6Td"/>
|
||||||
|
</connections>
|
||||||
|
</button>
|
||||||
|
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="5ZK-BG-o1t">
|
||||||
|
<rect key="frame" x="42" y="85" width="100" height="16"/>
|
||||||
|
<textFieldCell key="cell" lineBreakMode="clipping" title="PREF_GLOSHO:" id="xiD-8H-p5s">
|
||||||
|
<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>
|
||||||
|
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="31d-gd-auR">
|
||||||
|
<rect key="frame" x="18" y="153" width="124" height="16"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstAttribute="width" constant="120" id="8dt-Pg-wFI"/>
|
||||||
|
</constraints>
|
||||||
|
<textFieldCell key="cell" lineBreakMode="clipping" alignment="right" title="PREF_DYN_ICON:" id="E10-ss-Cdz">
|
||||||
|
<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>
|
||||||
|
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="1TO-9H-z2d">
|
||||||
|
<rect key="frame" x="148" y="60" width="101" height="14"/>
|
||||||
|
<textFieldCell key="cell" title="SHORTCUT_DESC" id="nYP-yi-DBf">
|
||||||
|
<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>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstAttribute="trailing" secondItem="JrH-aa-AzL" secondAttribute="trailing" constant="20" symbolic="YES" id="8iM-Xf-ShU"/>
|
||||||
|
<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="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="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="1TO-9H-z2d" firstAttribute="leading" secondItem="V7b-jv-oCB" secondAttribute="leading" id="Imk-o0-2fS"/>
|
||||||
|
<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="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="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="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 firstItem="GSr-K5-3yw" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="Pf1-A5-3Xz" secondAttribute="leading" constant="20" symbolic="YES" id="mTE-WD-54L"/>
|
||||||
|
<constraint firstItem="31d-gd-auR" firstAttribute="leading" secondItem="Pf1-A5-3Xz" secondAttribute="leading" constant="20" symbolic="YES" id="o0J-yT-TDX"/>
|
||||||
|
<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"/>
|
||||||
|
</constraints>
|
||||||
|
</view>
|
||||||
|
<connections>
|
||||||
|
<outlet property="buttonClearShortcut" destination="YsQ-AZ-Aei" id="1xo-hk-HgM"/>
|
||||||
|
<outlet property="buttonClose" destination="GSr-K5-3yw" id="d4I-Cf-gXD"/>
|
||||||
|
<outlet property="buttonDynamicIcon" destination="MEf-MN-oXt" id="qEN-Vg-EZS"/>
|
||||||
|
<outlet property="buttonSetShortcut" destination="V7b-jv-oCB" id="2aS-S4-cKR"/>
|
||||||
|
<outlet property="labelDynamicIcon" destination="JrH-aa-AzL" id="CFc-fF-oPq"/>
|
||||||
|
<outlet property="labelShortcut" destination="1TO-9H-z2d" id="paF-hK-78x"/>
|
||||||
|
<outlet property="leftLabelDynamicIcon" destination="31d-gd-auR" id="ANZ-Zs-4d7"/>
|
||||||
|
<outlet property="leftLabelGlobalShortcut" destination="5ZK-BG-o1t" id="73E-9i-cg8"/>
|
||||||
|
</connections>
|
||||||
|
</viewController>
|
||||||
|
<customObject id="eQC-8B-FkX" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||||
|
</objects>
|
||||||
|
<point key="canvasLocation" x="264" y="399.5"/>
|
||||||
|
</scene>
|
||||||
</scenes>
|
</scenes>
|
||||||
</document>
|
</document>
|
||||||
|
@@ -29,6 +29,11 @@ class PhpExtension {
|
|||||||
/// Whether the extension has been enabled.
|
/// Whether the extension has been enabled.
|
||||||
var enabled: Bool
|
var enabled: Bool
|
||||||
|
|
||||||
|
/// The file where this extension was located, but only the filename, not the full path to the .ini file.
|
||||||
|
var fileNameOnly: String {
|
||||||
|
return String(file.split(separator: "/").last ?? "php.ini")
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
This regular expression will allow us to identify lines which activate an extension.
|
This regular expression will allow us to identify lines which activate an extension.
|
||||||
|
|
||||||
@@ -41,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>[a-zA-Z]*).so"$"#
|
static let extensionRegex = #"^(extension=|zend_extension=|; extension=|; zend_extension=)(?<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.
|
||||||
@@ -52,7 +57,12 @@ class PhpExtension {
|
|||||||
let range = Range(match!.range(withName: "name"), in: line)!
|
let range = Range(match!.range(withName: "name"), in: line)!
|
||||||
|
|
||||||
self.line = line
|
self.line = line
|
||||||
self.name = line[range]
|
|
||||||
|
let fullPath = String(line[range])
|
||||||
|
.replacingOccurrences(of: "\"", with: "") // replace excess "
|
||||||
|
.replacingOccurrences(of: ".so", with: "") // replace excess .so
|
||||||
|
self.name = String(fullPath.split(separator: "/").last!) // take last segment
|
||||||
|
|
||||||
self.enabled = !line.contains(";")
|
self.enabled = !line.contains(";")
|
||||||
self.file = file
|
self.file = file
|
||||||
}
|
}
|
||||||
@@ -62,11 +72,11 @@ class PhpExtension {
|
|||||||
*/
|
*/
|
||||||
func toggle() {
|
func toggle() {
|
||||||
Actions.sed(
|
Actions.sed(
|
||||||
file: self.file,
|
file: file,
|
||||||
original: self.line,
|
original: line,
|
||||||
replacement: self.enabled ? "; \(self.line)" : self.line.replacingOccurrences(of: "; ", with: "")
|
replacement: enabled ? "; \(line)" : line.replacingOccurrences(of: "; ", with: "")
|
||||||
)
|
)
|
||||||
self.enabled = !self.enabled
|
enabled = !enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Static Methods
|
// MARK: - Static Methods
|
||||||
|
@@ -9,66 +9,79 @@ import Foundation
|
|||||||
|
|
||||||
class PhpInstallation {
|
class PhpInstallation {
|
||||||
|
|
||||||
var version: Version
|
var version: Version!
|
||||||
var configuration: Configuration
|
var configuration: Configuration!
|
||||||
var extensions: [PhpExtension]
|
var extensions: [PhpExtension]!
|
||||||
|
|
||||||
// MARK: - Computed
|
// MARK: - Computed
|
||||||
|
|
||||||
var formula: String {
|
var formula: String {
|
||||||
return (self.version.short == App.shared.brewPhpVersion) ? "php" : "php@\(self.version.short)"
|
return (version.short == App.shared.brewPhpVersion) ? "php" : "php@\(version.short)"
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Initializer
|
// MARK: - Initializer
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
// Show information about the current version
|
// Show information about the current version
|
||||||
self.version = Self.getVersion()
|
self.getVersion()
|
||||||
|
|
||||||
// If an error occurred, exit early
|
// If an error occurred, exit early
|
||||||
if (self.version.error) {
|
if (version.error) {
|
||||||
self.configuration = Configuration()
|
configuration = Configuration()
|
||||||
self.extensions = []
|
extensions = []
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load extension information
|
// Load extension information
|
||||||
let path = URL(fileURLWithPath: "\(Paths.etcPath)/php/\(self.version.short)/php.ini")
|
let path = URL(fileURLWithPath: "\(Paths.etcPath)/php/\(version.short)/php.ini")
|
||||||
self.extensions = PhpExtension.load(from: path)
|
extensions = PhpExtension.load(from: path)
|
||||||
|
|
||||||
// Get configuration values
|
// Get configuration values
|
||||||
self.configuration = Configuration(
|
configuration = Configuration(
|
||||||
memory_limit: Self.getByteCount(key: "memory_limit"),
|
memory_limit: self.getByteCount(key: "memory_limit"),
|
||||||
upload_max_filesize: Self.getByteCount(key: "upload_max_filesize"),
|
upload_max_filesize: self.getByteCount(key: "upload_max_filesize"),
|
||||||
post_max_size: Self.getByteCount(key: "post_max_size")
|
post_max_size: self.getByteCount(key: "post_max_size")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Return a list of .ini files parsed after php.ini
|
||||||
|
let paths = Command.execute(path: Paths.php, arguments: ["-r", "echo php_ini_scanned_files();"])
|
||||||
|
.replacingOccurrences(of: "\n", with: "")
|
||||||
|
.split(separator: ",")
|
||||||
|
.map { String($0) }
|
||||||
|
|
||||||
|
// See if any extensions are present in said .ini files
|
||||||
|
paths.forEach { (iniFilePath) in
|
||||||
|
let extensions = PhpExtension.load(from: URL(fileURLWithPath: iniFilePath))
|
||||||
|
if extensions.count > 0 {
|
||||||
|
self.extensions.append(contentsOf: extensions)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
When the app tries to retrieve the version, the installation is considered broken if the output is nothing,
|
When the app tries to retrieve the version, the installation is considered broken if the output is nothing,
|
||||||
_or_ if the output contains the word "Warning" or "Error". In normal situations this should not be the case.
|
_or_ if the output contains the word "Warning" or "Error". In normal situations this should not be the case.
|
||||||
*/
|
*/
|
||||||
private static func getVersion() -> Version {
|
private func getVersion() -> Void {
|
||||||
var versionStruct = Version()
|
self.version = Version()
|
||||||
let version = Command.execute(path: Paths.php, arguments: ["-r", "print phpversion();"])
|
|
||||||
|
let version = Command.execute(path: Paths.phpConfig, arguments: ["--version"], trimNewlines: true)
|
||||||
|
|
||||||
if (version == "" || version.contains("Warning") || version.contains("Error")) {
|
if (version == "" || version.contains("Warning") || version.contains("Error")) {
|
||||||
versionStruct.short = "💩 BROKEN"
|
self.version.short = "💩 BROKEN"
|
||||||
versionStruct.long = "";
|
self.version.long = ""
|
||||||
versionStruct.error = true
|
self.version.error = true
|
||||||
return versionStruct;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// That's the long version
|
// That's the long version
|
||||||
versionStruct.long = version
|
self.version.long = version
|
||||||
|
|
||||||
// Next up, let's strip away the minor version number
|
// Next up, let's strip away the minor version number
|
||||||
let segments = versionStruct.long.components(separatedBy: ".")
|
let segments = self.version.long.components(separatedBy: ".")
|
||||||
|
|
||||||
// Get the first two elements
|
// Get the first two elements
|
||||||
versionStruct.short = segments[0...1].joined(separator: ".")
|
self.version.short = segments[0...1].joined(separator: ".")
|
||||||
|
|
||||||
return versionStruct
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -84,7 +97,7 @@ class PhpInstallation {
|
|||||||
|
|
||||||
- Parameter key: The key of the `ini` value that needs to be retrieved. For example, you can use `memory_limit`.
|
- Parameter key: The key of the `ini` value that needs to be retrieved. For example, you can use `memory_limit`.
|
||||||
*/
|
*/
|
||||||
private static func getByteCount(key: String) -> String {
|
private func getByteCount(key: String) -> String {
|
||||||
let value = Command.execute(path: Paths.php, arguments: ["-r", "echo ini_get('\(key)');"])
|
let value = Command.execute(path: Paths.php, arguments: ["-r", "echo ini_get('\(key)');"])
|
||||||
|
|
||||||
// Check if the value is unlimited
|
// Check if the value is unlimited
|
||||||
@@ -98,6 +111,28 @@ class PhpInstallation {
|
|||||||
return (match == nil) ? "⚠️" : "\(value)B"
|
return (match == nil) ? "⚠️" : "\(value)B"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func notifyAboutBrokenPhpFpm() {
|
||||||
|
if !self.checkPhpFpmStatus() {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
Alert.notify(
|
||||||
|
message: "alert.php_fpm_broken.title".localized,
|
||||||
|
info: "alert.php_fpm_broken.info".localized
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func checkPhpFpmStatus() -> Bool {
|
||||||
|
if self.version.short == "5.6" {
|
||||||
|
// The main PHP config file should contain `valet.sock` and then we're probably fine?
|
||||||
|
let fileName = "\(Paths.etcPath)/php/5.6/php-fpm.conf"
|
||||||
|
return Shell.pipe("cat \(fileName)").contains("valet.sock")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure to check if valet-fpm.conf exists. If it does, we should be fine :)
|
||||||
|
return Shell.fileExists("\(Paths.etcPath)/php/\(self.version.short)/php-fpm.d/valet-fpm.conf")
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Structs
|
// MARK: - Structs
|
||||||
|
|
||||||
struct Version {
|
struct Version {
|
||||||
|
18
phpmon/Domain/Extensions/NSMenuExtension.swift
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
//
|
||||||
|
// NSMenuExtension.swift
|
||||||
|
// PHP Monitor
|
||||||
|
//
|
||||||
|
// Created by Nico Verbruggen on 14/04/2021.
|
||||||
|
// Copyright © 2021 Nico Verbruggen. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Cocoa
|
||||||
|
|
||||||
|
extension NSMenu {
|
||||||
|
|
||||||
|
open func addItem(_ newItem: NSMenuItem, withKeyModifier modifier: NSEvent.ModifierFlags) {
|
||||||
|
newItem.keyEquivalentModifierMask = modifier
|
||||||
|
self.addItem(newItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -7,10 +7,12 @@
|
|||||||
|
|
||||||
import Cocoa
|
import Cocoa
|
||||||
|
|
||||||
class MainMenu: NSObject, NSWindowDelegate {
|
class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate {
|
||||||
|
|
||||||
static let shared = MainMenu()
|
static let shared = MainMenu()
|
||||||
|
|
||||||
|
weak var menuDelegate: NSMenuDelegate? = nil
|
||||||
|
|
||||||
/**
|
/**
|
||||||
The status bar item with variable length.
|
The status bar item with variable length.
|
||||||
*/
|
*/
|
||||||
@@ -25,11 +27,12 @@ class MainMenu: NSObject, NSWindowDelegate {
|
|||||||
*/
|
*/
|
||||||
func startup() {
|
func startup() {
|
||||||
// Start with the icon
|
// Start with the icon
|
||||||
self.setStatusBar(image: NSImage(named: NSImage.Name("StatusBarIcon"))!)
|
setStatusBar(image: NSImage(named: NSImage.Name("StatusBarIcon"))!)
|
||||||
|
|
||||||
// Perform environment boot checks
|
// Perform environment boot checks
|
||||||
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
|
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
|
||||||
Startup().checkEnvironment(success: { self.onEnvironmentPass() },
|
Startup().checkEnvironment(success: { onEnvironmentPass() },
|
||||||
failure: { self.onEnvironmentFail() }
|
failure: { onEnvironmentFail() }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -39,13 +42,17 @@ class MainMenu: NSObject, NSWindowDelegate {
|
|||||||
*/
|
*/
|
||||||
private func onEnvironmentPass() {
|
private func onEnvironmentPass() {
|
||||||
App.shared.availablePhpVersions = Actions.detectPhpVersions()
|
App.shared.availablePhpVersions = Actions.detectPhpVersions()
|
||||||
self.updatePhpVersionInStatusBar()
|
updatePhpVersionInStatusBar()
|
||||||
|
|
||||||
|
let installation = App.phpInstall!
|
||||||
|
installation.notifyAboutBrokenPhpFpm()
|
||||||
|
|
||||||
// Schedule a request to fetch the PHP version every 60 seconds
|
// Schedule a request to fetch the PHP version every 60 seconds
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async { [self] in
|
||||||
App.shared.timer = Timer.scheduledTimer(
|
App.shared.timer = Timer.scheduledTimer(
|
||||||
timeInterval: 60,
|
timeInterval: 60,
|
||||||
target: self,
|
target: self,
|
||||||
selector: #selector(self.updatePhpVersionInStatusBar),
|
selector: #selector(updatePhpVersionInStatusBar),
|
||||||
userInfo: nil,
|
userInfo: nil,
|
||||||
repeats: true
|
repeats: true
|
||||||
)
|
)
|
||||||
@@ -56,7 +63,7 @@ class MainMenu: NSObject, NSWindowDelegate {
|
|||||||
When the environment is not OK, present an alert to inform the user.
|
When the environment is not OK, present an alert to inform the user.
|
||||||
*/
|
*/
|
||||||
private func onEnvironmentFail() {
|
private func onEnvironmentFail() {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async { [self] in
|
||||||
let close = Alert.present(
|
let close = Alert.present(
|
||||||
messageText: "alert.cannot_start.title".localized,
|
messageText: "alert.cannot_start.title".localized,
|
||||||
informativeText: "alert.cannot_start.info".localized,
|
informativeText: "alert.cannot_start.info".localized,
|
||||||
@@ -68,7 +75,7 @@ class MainMenu: NSObject, NSWindowDelegate {
|
|||||||
exit(1)
|
exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.startup()
|
startup()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,7 +84,7 @@ class MainMenu: NSObject, NSWindowDelegate {
|
|||||||
*/
|
*/
|
||||||
func update() {
|
func update() {
|
||||||
// Update the menu item on the main thread
|
// Update the menu item on the main thread
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async { [self] in
|
||||||
// Create a new menu
|
// Create a new menu
|
||||||
let menu = StatusMenu()
|
let menu = StatusMenu()
|
||||||
|
|
||||||
@@ -94,15 +101,17 @@ class MainMenu: NSObject, NSWindowDelegate {
|
|||||||
menu.addItem(NSMenuItem.separator())
|
menu.addItem(NSMenuItem.separator())
|
||||||
|
|
||||||
// Add about & quit menu items
|
// Add about & quit menu items
|
||||||
menu.addItem(NSMenuItem(title: "mi_about".localized, action: #selector(self.openAbout), keyEquivalent: ""))
|
menu.addItem(NSMenuItem(title: "mi_preferences".localized, action: #selector(openPrefs), keyEquivalent: ","))
|
||||||
menu.addItem(NSMenuItem(title: "mi_quit".localized, action: #selector(self.terminateApp), keyEquivalent: "q"))
|
menu.addItem(NSMenuItem(title: "mi_about".localized, action: #selector(openAbout), keyEquivalent: ""))
|
||||||
|
menu.addItem(NSMenuItem(title: "mi_quit".localized, action: #selector(terminateApp), keyEquivalent: "q"))
|
||||||
|
|
||||||
// Make sure every item can be interacted with
|
// Make sure every item can be interacted with
|
||||||
menu.items.forEach({ (item) in
|
menu.items.forEach({ (item) in
|
||||||
item.target = self
|
item.target = self
|
||||||
})
|
})
|
||||||
|
|
||||||
self.statusItem.menu = menu
|
statusItem.menu = menu
|
||||||
|
statusItem.menu?.delegate = self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,7 +119,7 @@ class MainMenu: NSObject, NSWindowDelegate {
|
|||||||
Sets the status bar image based on a version string.
|
Sets the status bar image based on a version string.
|
||||||
*/
|
*/
|
||||||
func setStatusBarImage(version: String) {
|
func setStatusBarImage(version: String) {
|
||||||
self.setStatusBar(
|
setStatusBar(
|
||||||
image: MenuBarImageGenerator.textToImage(text: version)
|
image: MenuBarImageGenerator.textToImage(text: version)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -136,17 +145,18 @@ class MainMenu: NSObject, NSWindowDelegate {
|
|||||||
- Parameter execute: Callback of the work that needs to happen.
|
- Parameter execute: Callback of the work that needs to happen.
|
||||||
- Parameter completion: Callback that is fired when the work is done.
|
- Parameter completion: Callback that is fired when the work is done.
|
||||||
*/
|
*/
|
||||||
private func waitAndExecute(_ execute: @escaping () -> Void, _ completion: @escaping () -> Void = {})
|
private func waitAndExecute(_ execute: @escaping () -> Void, completion: @escaping () -> Void = {})
|
||||||
{
|
{
|
||||||
App.shared.busy = true
|
App.shared.busy = true
|
||||||
self.setBusyImage()
|
setBusyImage()
|
||||||
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
|
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
|
||||||
self.update()
|
update()
|
||||||
execute()
|
execute()
|
||||||
App.shared.busy = false
|
App.shared.busy = false
|
||||||
DispatchQueue.main.async {
|
|
||||||
self.updatePhpVersionInStatusBar()
|
DispatchQueue.main.async { [self] in
|
||||||
self.update()
|
updatePhpVersionInStatusBar()
|
||||||
|
update()
|
||||||
completion()
|
completion()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -156,80 +166,122 @@ class MainMenu: NSObject, NSWindowDelegate {
|
|||||||
|
|
||||||
@objc func updatePhpVersionInStatusBar() {
|
@objc func updatePhpVersionInStatusBar() {
|
||||||
App.shared.currentInstall = PhpInstallation()
|
App.shared.currentInstall = PhpInstallation()
|
||||||
|
refreshIcon()
|
||||||
|
update()
|
||||||
|
}
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
func refreshIcon() {
|
||||||
if (App.shared.busy) {
|
DispatchQueue.main.async { [self] in
|
||||||
self.setStatusBar(image: NSImage(named: NSImage.Name("StatusBarIcon"))!)
|
if (App.busy) {
|
||||||
|
setStatusBar(image: NSImage(named: NSImage.Name("StatusBarIcon"))!)
|
||||||
} else {
|
} else {
|
||||||
self.setStatusBarImage(version: App.phpInstall!.version.short)
|
if Preferences.preferences[.shouldDisplayDynamicIcon] as! Bool == false {
|
||||||
|
// Static icon has been requested
|
||||||
|
setStatusBar(image: NSImage(named: NSImage.Name("StatusBarIconStatic"))!)
|
||||||
|
} else {
|
||||||
|
// The dynamic icon has been requested
|
||||||
|
setStatusBarImage(version: App.phpInstall!.version.short)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.update()
|
@objc func reloadPhpMonitorMenu() {
|
||||||
|
waitAndExecute {
|
||||||
|
// This automatically reloads the menu
|
||||||
|
print("Reloading information about the PHP installation...")
|
||||||
|
} completion: {
|
||||||
|
// Add a slight delay to make sure it loads the new menu
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||||
|
// Open the menu again
|
||||||
|
MainMenu.shared.statusItem.button?.performClick(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func setBusyImage() {
|
@objc func setBusyImage() {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async { [self] in
|
||||||
self.setStatusBar(image: NSImage(named: NSImage.Name("StatusBarIcon"))!)
|
setStatusBar(image: NSImage(named: NSImage.Name("StatusBarIcon"))!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Actions
|
// MARK: - Actions
|
||||||
|
|
||||||
@objc func restartPhpFpm() {
|
@objc func restartPhpFpm() {
|
||||||
self.waitAndExecute({
|
waitAndExecute {
|
||||||
Actions.restartPhpFpm()
|
Actions.restartPhpFpm()
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func restartAllServices() {
|
@objc func restartAllServices() {
|
||||||
self.waitAndExecute({
|
waitAndExecute {
|
||||||
Actions.restartDnsMasq()
|
Actions.restartDnsMasq()
|
||||||
Actions.restartPhpFpm()
|
Actions.restartPhpFpm()
|
||||||
Actions.restartNginx()
|
Actions.restartNginx()
|
||||||
})
|
} completion: {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
LocalNotification.send(
|
||||||
|
title: "notification.services_restarted".localized,
|
||||||
|
subtitle: "notification.services_restarted_desc".localized
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func stopAllServices() {
|
||||||
|
waitAndExecute {
|
||||||
|
Actions.stopAllServices()
|
||||||
|
} completion: {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
LocalNotification.send(
|
||||||
|
title: "notification.services_stopped".localized,
|
||||||
|
subtitle: "notification.services_stopped_desc".localized
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func restartNginx() {
|
@objc func restartNginx() {
|
||||||
self.waitAndExecute({
|
waitAndExecute {
|
||||||
Actions.restartNginx()
|
Actions.restartNginx()
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func restartDnsMasq() {
|
@objc func restartDnsMasq() {
|
||||||
self.waitAndExecute({
|
waitAndExecute {
|
||||||
Actions.restartDnsMasq()
|
Actions.restartDnsMasq()
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func toggleExtension(sender: ExtensionMenuItem) {
|
@objc func toggleExtension(sender: ExtensionMenuItem) {
|
||||||
self.waitAndExecute({
|
waitAndExecute {
|
||||||
// Toggle that extension
|
|
||||||
print("Toggling extension '\(sender.phpExtension!.name)'")
|
|
||||||
sender.phpExtension?.toggle()
|
sender.phpExtension?.toggle()
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func openPhpInfo() {
|
@objc func openPhpInfo() {
|
||||||
self.waitAndExecute({
|
waitAndExecute {
|
||||||
|
// Write a file called `phpmon_phpinfo.php` to /tmp
|
||||||
try! "<?php phpinfo();".write(toFile: "/tmp/phpmon_phpinfo.php", atomically: true, encoding: .utf8)
|
try! "<?php phpinfo();".write(toFile: "/tmp/phpmon_phpinfo.php", atomically: true, encoding: .utf8)
|
||||||
|
|
||||||
|
// Tell php-cgi to run the PHP and output as an .html file
|
||||||
Shell.run("\(Paths.binPath)/php-cgi -q /tmp/phpmon_phpinfo.php > /tmp/phpmon_phpinfo.html")
|
Shell.run("\(Paths.binPath)/php-cgi -q /tmp/phpmon_phpinfo.php > /tmp/phpmon_phpinfo.html")
|
||||||
}, {
|
} completion: {
|
||||||
|
// When this has been completed, open the URL to the file in the browser
|
||||||
NSWorkspace.shared.open(URL(string: "file:///private/tmp/phpmon_phpinfo.html")!)
|
NSWorkspace.shared.open(URL(string: "file:///private/tmp/phpmon_phpinfo.html")!)
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func forceRestartLatestPhp() {
|
@objc func forceRestartLatestPhp() {
|
||||||
// Tell the user the switch is about to occur
|
// Tell the user the switch is about to occur
|
||||||
Alert.notify(message: "alert.force_reload.title".localized, info: "alert.force_reload.info".localized)
|
Alert.notify(message: "alert.force_reload.title".localized, info: "alert.force_reload.info".localized)
|
||||||
|
|
||||||
// Start switching
|
// Start switching
|
||||||
self.waitAndExecute(
|
waitAndExecute {
|
||||||
{ Actions.fixMyPhp() },
|
Actions.fixMyPhp()
|
||||||
{ Alert.notify(
|
} completion: {
|
||||||
message: "alert.force_reload_done.title".localized,
|
Alert.notify(message: "alert.force_reload_done.title".localized, info: "alert.force_reload_done.info".localized)
|
||||||
info: "alert.force_reload_done.info".localized
|
}
|
||||||
) }
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func openActiveConfigFolder() {
|
@objc func openActiveConfigFolder() {
|
||||||
@@ -243,42 +295,50 @@ class MainMenu: NSObject, NSWindowDelegate {
|
|||||||
Actions.openPhpConfigFolder(version: App.phpInstall!.version.short)
|
Actions.openPhpConfigFolder(version: App.phpInstall!.version.short)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc func openGlobalComposerFolder() {
|
||||||
|
Actions.openGlobalComposerFolder()
|
||||||
|
}
|
||||||
|
|
||||||
@objc func openValetConfigFolder() {
|
@objc func openValetConfigFolder() {
|
||||||
Actions.openValetConfigFolder()
|
Actions.openValetConfigFolder()
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func switchToPhpVersion(sender: PhpMenuItem) {
|
@objc func switchToPhpVersion(sender: PhpMenuItem) {
|
||||||
print("Switching to: PHP \(sender.version)")
|
setBusyImage()
|
||||||
|
|
||||||
self.setBusyImage()
|
|
||||||
App.shared.busy = true
|
App.shared.busy = true
|
||||||
|
|
||||||
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
|
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
|
||||||
// Update the PHP version in the status bar
|
// Update the PHP version in the status bar
|
||||||
self.updatePhpVersionInStatusBar()
|
updatePhpVersionInStatusBar()
|
||||||
|
|
||||||
// Update the menu
|
// Update the menu
|
||||||
self.update()
|
update()
|
||||||
|
|
||||||
|
let completion = {
|
||||||
|
// Mark as no longer busy
|
||||||
|
App.shared.busy = false
|
||||||
|
|
||||||
|
// Perform UI updates on main thread
|
||||||
|
DispatchQueue.main.async { [self] in
|
||||||
|
updatePhpVersionInStatusBar()
|
||||||
|
update()
|
||||||
|
|
||||||
|
// Send a notification that the switch has been completed
|
||||||
|
LocalNotification.send(
|
||||||
|
title: String(format: "notification.version_changed_title".localized, sender.version),
|
||||||
|
subtitle: String(format: "notification.version_changed_desc".localized, sender.version)
|
||||||
|
)
|
||||||
|
|
||||||
|
App.phpInstall?.notifyAboutBrokenPhpFpm()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Switch the PHP version
|
// Switch the PHP version
|
||||||
Actions.switchToPhpVersion(
|
Actions.switchToPhpVersion(
|
||||||
version: sender.version,
|
version: sender.version,
|
||||||
availableVersions: App.shared.availablePhpVersions
|
availableVersions: App.shared.availablePhpVersions,
|
||||||
|
completed: completion
|
||||||
)
|
)
|
||||||
|
|
||||||
// Mark as no longer busy
|
|
||||||
App.shared.busy = false
|
|
||||||
|
|
||||||
// Perform UI updates on main thread
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
self.updatePhpVersionInStatusBar()
|
|
||||||
self.update()
|
|
||||||
// Send a notification that the switch has been completed
|
|
||||||
LocalNotification.send(
|
|
||||||
title: String(format: "notification.version_changed_title".localized, sender.version),
|
|
||||||
subtitle: String(format: "notification.version_changed_desc".localized, sender.version)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,7 +347,23 @@ class MainMenu: NSObject, NSWindowDelegate {
|
|||||||
NSApplication.shared.orderFrontStandardAboutPanel()
|
NSApplication.shared.orderFrontStandardAboutPanel()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc func openPrefs() {
|
||||||
|
PrefsVC.show()
|
||||||
|
}
|
||||||
|
|
||||||
@objc func terminateApp() {
|
@objc func terminateApp() {
|
||||||
NSApplication.shared.terminate(nil)
|
NSApplication.shared.terminate(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Menu Delegate
|
||||||
|
|
||||||
|
func menuWillOpen(_ menu: NSMenu) {
|
||||||
|
// Make sure the shortcut key does not trigger this when the menu is open
|
||||||
|
App.shared.shortcutHotkey?.isPaused = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func menuDidClose(_ menu: NSMenu) {
|
||||||
|
// When the menu is closed, allow the shortcut to work again
|
||||||
|
App.shared.shortcutHotkey?.isPaused = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -15,18 +15,18 @@ class StatusMenu : NSMenu {
|
|||||||
|
|
||||||
if App.phpInstall!.version.error {
|
if App.phpInstall!.version.error {
|
||||||
for message in ["mi_php_broken_1", "mi_php_broken_2", "mi_php_broken_3", "mi_php_broken_4"] {
|
for message in ["mi_php_broken_1", "mi_php_broken_2", "mi_php_broken_3", "mi_php_broken_4"] {
|
||||||
self.addItem(NSMenuItem(title: message.localized, action: nil, keyEquivalent: ""))
|
addItem(NSMenuItem(title: message.localized, action: nil, keyEquivalent: ""))
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let phpVersionText = "\("mi_php_version".localized) \(App.phpInstall!.version.long)"
|
let phpVersionText = "\("mi_php_version".localized) \(App.phpInstall!.version.long)"
|
||||||
self.addItem(HeaderView.asMenuItem(text: phpVersionText))
|
addItem(HeaderView.asMenuItem(text: phpVersionText))
|
||||||
}
|
}
|
||||||
|
|
||||||
func addPhpActionMenuItems() {
|
func addPhpActionMenuItems() {
|
||||||
if App.busy {
|
if App.busy {
|
||||||
self.addItem(NSMenuItem(title: "mi_busy".localized, action: nil, keyEquivalent: ""))
|
addItem(NSMenuItem(title: "mi_busy".localized, action: nil, keyEquivalent: ""))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,11 +58,15 @@ class StatusMenu : NSMenu {
|
|||||||
private func addServicesMenuItems() {
|
private func addServicesMenuItems() {
|
||||||
self.addItem(HeaderView.asMenuItem(text: "mi_active_services".localized))
|
self.addItem(HeaderView.asMenuItem(text: "mi_active_services".localized))
|
||||||
|
|
||||||
let services = NSMenuItem(title: "mi_restart_specific".localized, action: nil, keyEquivalent: "")
|
let services = NSMenuItem(title: "mi_manage_services".localized, action: nil, keyEquivalent: "")
|
||||||
let servicesMenu = NSMenu()
|
let servicesMenu = NSMenu()
|
||||||
servicesMenu.addItem(NSMenuItem(title: "mi_restart_dnsmasq".localized, action: #selector(MainMenu.restartDnsMasq), keyEquivalent: "d"))
|
servicesMenu.addItem(NSMenuItem(title: "mi_restart_dnsmasq".localized, action: #selector(MainMenu.restartDnsMasq), keyEquivalent: "d"))
|
||||||
servicesMenu.addItem(NSMenuItem(title: "mi_restart_php_fpm".localized, action: #selector(MainMenu.restartPhpFpm), keyEquivalent: "p"))
|
servicesMenu.addItem(NSMenuItem(title: "mi_restart_php_fpm".localized, action: #selector(MainMenu.restartPhpFpm), keyEquivalent: "p"))
|
||||||
servicesMenu.addItem(NSMenuItem(title: "mi_restart_nginx".localized, action: #selector(MainMenu.restartNginx), keyEquivalent: "n"))
|
servicesMenu.addItem(NSMenuItem(title: "mi_restart_nginx".localized, action: #selector(MainMenu.restartNginx), keyEquivalent: "n"))
|
||||||
|
servicesMenu.addItem(
|
||||||
|
NSMenuItem(title: "mi_stop_all_services".localized, action: #selector(MainMenu.stopAllServices), keyEquivalent: "s"),
|
||||||
|
withKeyModifier: [.command, .shift]
|
||||||
|
)
|
||||||
for item in servicesMenu.items {
|
for item in servicesMenu.items {
|
||||||
item.target = MainMenu.shared
|
item.target = MainMenu.shared
|
||||||
}
|
}
|
||||||
@@ -81,6 +85,7 @@ class StatusMenu : NSMenu {
|
|||||||
// Configuration
|
// Configuration
|
||||||
self.addItem(HeaderView.asMenuItem(text: "mi_configuration".localized))
|
self.addItem(HeaderView.asMenuItem(text: "mi_configuration".localized))
|
||||||
self.addItem(NSMenuItem(title: "mi_valet_config".localized, action: #selector(MainMenu.openValetConfigFolder), keyEquivalent: "v"))
|
self.addItem(NSMenuItem(title: "mi_valet_config".localized, action: #selector(MainMenu.openValetConfigFolder), keyEquivalent: "v"))
|
||||||
|
self.addItem(NSMenuItem(title: "mi_global_composer".localized, action: #selector(MainMenu.openGlobalComposerFolder), keyEquivalent: "g"))
|
||||||
self.addItem(NSMenuItem(title: "mi_php_config".localized, action: #selector(MainMenu.openActiveConfigFolder), keyEquivalent: "c"))
|
self.addItem(NSMenuItem(title: "mi_php_config".localized, action: #selector(MainMenu.openActiveConfigFolder), keyEquivalent: "c"))
|
||||||
self.addItem(NSMenuItem(title: "mi_phpinfo".localized, action: #selector(MainMenu.openPhpInfo), keyEquivalent: "i"))
|
self.addItem(NSMenuItem(title: "mi_phpinfo".localized, action: #selector(MainMenu.openPhpInfo), keyEquivalent: "i"))
|
||||||
|
|
||||||
@@ -93,9 +98,9 @@ class StatusMenu : NSMenu {
|
|||||||
// Stats
|
// Stats
|
||||||
self.addItem(NSMenuItem.separator())
|
self.addItem(NSMenuItem.separator())
|
||||||
self.addItem(StatsView.asMenuItem(
|
self.addItem(StatsView.asMenuItem(
|
||||||
memory: stats.memory_limit,
|
memory: stats!.memory_limit,
|
||||||
post: stats.post_max_size,
|
post: stats!.post_max_size,
|
||||||
upload: stats.upload_max_filesize)
|
upload: stats!.upload_max_filesize)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Extensions
|
// Extensions
|
||||||
@@ -106,18 +111,33 @@ class StatusMenu : NSMenu {
|
|||||||
self.addItem(NSMenuItem(title: "mi_no_extensions_detected".localized, action: nil, keyEquivalent: ""))
|
self.addItem(NSMenuItem(title: "mi_no_extensions_detected".localized, action: nil, keyEquivalent: ""))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var shortcutKey = 1
|
||||||
for phpExtension in App.phpInstall!.extensions {
|
for phpExtension in App.phpInstall!.extensions {
|
||||||
self.addExtensionItem(phpExtension)
|
self.addExtensionItem(phpExtension, shortcutKey)
|
||||||
|
shortcutKey += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.addItem(NSMenuItem.separator())
|
||||||
|
|
||||||
|
self.addItem(NSMenuItem(title: "mi_php_refresh".localized, action: #selector(MainMenu.reloadPhpMonitorMenu), keyEquivalent: "r"))
|
||||||
}
|
}
|
||||||
|
|
||||||
private func addExtensionItem(_ phpExtension: PhpExtension) {
|
private func addExtensionItem(_ phpExtension: PhpExtension, _ shortcutKey: Int) {
|
||||||
|
let keyEquivalent = shortcutKey < 9 ? "\(shortcutKey)" : ""
|
||||||
|
|
||||||
let menuItem = ExtensionMenuItem(
|
let menuItem = ExtensionMenuItem(
|
||||||
title: "\(phpExtension.name.capitalized) (php.ini)",
|
title: "\(phpExtension.name) (\(phpExtension.fileNameOnly))",
|
||||||
action: #selector(MainMenu.toggleExtension), keyEquivalent: ""
|
action: #selector(MainMenu.toggleExtension),
|
||||||
|
keyEquivalent: keyEquivalent
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if menuItem.keyEquivalent != "" {
|
||||||
|
menuItem.keyEquivalentModifierMask = [.option]
|
||||||
|
}
|
||||||
|
|
||||||
menuItem.state = phpExtension.enabled ? .on : .off
|
menuItem.state = phpExtension.enabled ? .on : .off
|
||||||
menuItem.phpExtension = phpExtension
|
menuItem.phpExtension = phpExtension
|
||||||
|
|
||||||
self.addItem(menuItem)
|
self.addItem(menuItem)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,75 @@
|
|||||||
|
//
|
||||||
|
// GlobalKeybindPreference.swift
|
||||||
|
// PHP Monitor
|
||||||
|
//
|
||||||
|
// Created by Nico Verbruggen on 15/04/2021.
|
||||||
|
// Copyright © 2021 Nico Verbruggen. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct GlobalKeybindPreference: Codable, CustomStringConvertible {
|
||||||
|
|
||||||
|
// MARK: - Internal variables
|
||||||
|
|
||||||
|
let function : Bool
|
||||||
|
let control : Bool
|
||||||
|
let command : Bool
|
||||||
|
let shift : Bool
|
||||||
|
let option : Bool
|
||||||
|
let capsLock : Bool
|
||||||
|
let carbonFlags : UInt32
|
||||||
|
let characters : String?
|
||||||
|
let keyCode : UInt32
|
||||||
|
|
||||||
|
// MARK: - How the keybind is display in Preferences
|
||||||
|
|
||||||
|
var description: String {
|
||||||
|
var stringBuilder = ""
|
||||||
|
if self.function {
|
||||||
|
stringBuilder += "Fn"
|
||||||
|
}
|
||||||
|
if self.control {
|
||||||
|
stringBuilder += "⌃"
|
||||||
|
}
|
||||||
|
if self.option {
|
||||||
|
stringBuilder += "⌥"
|
||||||
|
}
|
||||||
|
if self.command {
|
||||||
|
stringBuilder += "⌘"
|
||||||
|
}
|
||||||
|
if self.shift {
|
||||||
|
stringBuilder += "⇧"
|
||||||
|
}
|
||||||
|
if self.capsLock {
|
||||||
|
stringBuilder += "⇪"
|
||||||
|
}
|
||||||
|
if let characters = self.characters {
|
||||||
|
stringBuilder += characters.uppercased()
|
||||||
|
}
|
||||||
|
return "\(stringBuilder)"
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Persisting data to UserDefaults (as JSON)
|
||||||
|
|
||||||
|
public func toJson() -> String {
|
||||||
|
let jsonData = try! JSONEncoder().encode(self)
|
||||||
|
return String(data: jsonData, encoding: .utf8)!
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func fromJson(_ string: String?) -> GlobalKeybindPreference? {
|
||||||
|
if string == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if let jsonData = string!.data(using: .utf8) {
|
||||||
|
let decoder = JSONDecoder()
|
||||||
|
do {
|
||||||
|
return try decoder.decode(GlobalKeybindPreference.self, from: jsonData)
|
||||||
|
} catch {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
56
phpmon/Domain/Preferences/Preferences.swift
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
//
|
||||||
|
// Preferences.swift
|
||||||
|
// PHP Monitor
|
||||||
|
//
|
||||||
|
// Created by Nico Verbruggen on 30/03/2021.
|
||||||
|
// Copyright © 2021 Nico Verbruggen. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
enum PreferenceName: String {
|
||||||
|
case shouldDisplayDynamicIcon = "use_dynamic_icon"
|
||||||
|
case globalHotkey = "global_hotkey"
|
||||||
|
}
|
||||||
|
|
||||||
|
class Preferences {
|
||||||
|
|
||||||
|
static func handleFirstTimeLaunch() {
|
||||||
|
let launchedBefore = UserDefaults.standard.bool(forKey: "launched_before")
|
||||||
|
|
||||||
|
if launchedBefore {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
UserDefaults.standard.setValue(true, forKey: PreferenceName.shouldDisplayDynamicIcon.rawValue)
|
||||||
|
UserDefaults.standard.setValue(true, forKey: "launched_before")
|
||||||
|
UserDefaults.standard.synchronize()
|
||||||
|
|
||||||
|
print("Saving first-time preferences!")
|
||||||
|
}
|
||||||
|
|
||||||
|
static func retrieve() -> [PreferenceName: Any] {
|
||||||
|
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?] {
|
||||||
|
return Preferences.retrieve()
|
||||||
|
}
|
||||||
|
|
||||||
|
static func update(_ preference: PreferenceName, value: Any?) {
|
||||||
|
if (value == nil) {
|
||||||
|
UserDefaults.standard.removeObject(forKey: preference.rawValue)
|
||||||
|
} else {
|
||||||
|
UserDefaults.standard.setValue(value, forKey: preference.rawValue)
|
||||||
|
}
|
||||||
|
UserDefaults.standard.synchronize()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
185
phpmon/Domain/Preferences/PrefsVC.swift
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
//
|
||||||
|
// PrefsVC.swift
|
||||||
|
// PHP Monitor
|
||||||
|
//
|
||||||
|
// Created by Nico Verbruggen on 30/03/2021.
|
||||||
|
// Copyright © 2021 Nico Verbruggen. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Cocoa
|
||||||
|
import HotKey
|
||||||
|
import Carbon
|
||||||
|
|
||||||
|
class PrefsVC: NSViewController {
|
||||||
|
|
||||||
|
@IBOutlet weak var leftLabelDynamicIcon: NSTextField!
|
||||||
|
@IBOutlet weak var leftLabelGlobalShortcut: NSTextField!
|
||||||
|
|
||||||
|
@IBOutlet weak var buttonDynamicIcon: NSButton!
|
||||||
|
@IBOutlet weak var labelDynamicIcon: NSTextField!
|
||||||
|
@IBOutlet weak var buttonClose: NSButton!
|
||||||
|
|
||||||
|
@IBOutlet weak var buttonSetShortcut: NSButton!
|
||||||
|
@IBOutlet weak var buttonClearShortcut: NSButton!
|
||||||
|
@IBOutlet weak var labelShortcut: NSTextField!
|
||||||
|
|
||||||
|
// MARK: - Display
|
||||||
|
|
||||||
|
public static func show(delegate: NSWindowDelegate? = nil) {
|
||||||
|
if (App.shared.windowController == nil) {
|
||||||
|
let vc = NSStoryboard(name: "Main", bundle: nil)
|
||||||
|
.instantiateController(withIdentifier: "preferences") as! PrefsVC
|
||||||
|
let window = NSWindow(contentViewController: vc)
|
||||||
|
|
||||||
|
window.title = "prefs.title".localized
|
||||||
|
window.delegate = delegate
|
||||||
|
window.styleMask = [.titled, .closable]
|
||||||
|
|
||||||
|
App.shared.windowController = PrefsWC(window: window)
|
||||||
|
}
|
||||||
|
|
||||||
|
App.shared.windowController!.showWindow(self)
|
||||||
|
NSApp.activate(ignoringOtherApps: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Lifecycle
|
||||||
|
|
||||||
|
override func viewWillAppear() {
|
||||||
|
loadLocalization()
|
||||||
|
loadDynamicIconFromPreferences()
|
||||||
|
loadGlobalKeybindFromPreferences()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewWillDisappear() {
|
||||||
|
if self.listeningForGlobalHotkey {
|
||||||
|
listeningForGlobalHotkey = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func loadLocalization() {
|
||||||
|
// Dynamic icon
|
||||||
|
leftLabelDynamicIcon.stringValue = "prefs.dynamic_icon".localized
|
||||||
|
labelDynamicIcon.stringValue = "prefs.dynamic_icon_desc".localized
|
||||||
|
buttonDynamicIcon.title = "prefs.dynamic_icon_title".localized
|
||||||
|
|
||||||
|
// Global Shortcut
|
||||||
|
leftLabelGlobalShortcut.stringValue = "prefs.global_shortcut".localized
|
||||||
|
labelShortcut.stringValue = "prefs.shortcut_desc".localized
|
||||||
|
buttonSetShortcut.title = "prefs.shortcut_set".localized
|
||||||
|
buttonClearShortcut.title = "prefs.shortcut_clear".localized
|
||||||
|
|
||||||
|
// Close button
|
||||||
|
buttonClose.title = "prefs.close".localized
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Dynamic Icon Preference
|
||||||
|
|
||||||
|
func loadDynamicIconFromPreferences() {
|
||||||
|
let shouldDisplay = Preferences.preferences[.shouldDisplayDynamicIcon] as! Bool == true
|
||||||
|
self.buttonDynamicIcon.state = shouldDisplay ? .on : .off
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Shortcut Preference
|
||||||
|
// Adapted from: https://dev.to/mitchartemis/creating-a-global-configurable-shortcut-for-macos-apps-in-swift-25e9
|
||||||
|
|
||||||
|
var listeningForGlobalHotkey = false {
|
||||||
|
didSet {
|
||||||
|
if listeningForGlobalHotkey {
|
||||||
|
DispatchQueue.main.async { [weak self] in
|
||||||
|
self?.buttonSetShortcut.highlight(true)
|
||||||
|
self?.buttonSetShortcut.title = "prefs.shortcut_listening".localized
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.async { [weak self] in
|
||||||
|
self?.buttonSetShortcut.highlight(false)
|
||||||
|
self?.loadGlobalKeybindFromPreferences()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadGlobalKeybindFromPreferences() {
|
||||||
|
let globalKeybind = GlobalKeybindPreference.fromJson(Preferences.preferences[.globalHotkey] as! String?)
|
||||||
|
|
||||||
|
if (globalKeybind != nil) {
|
||||||
|
updateKeybindButton(globalKeybind!)
|
||||||
|
} else {
|
||||||
|
buttonSetShortcut.title = "prefs.shortcut_set".localized
|
||||||
|
}
|
||||||
|
|
||||||
|
buttonClearShortcut.isEnabled = globalKeybind != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateGlobalShortcut(_ event : NSEvent) {
|
||||||
|
self.listeningForGlobalHotkey = false
|
||||||
|
|
||||||
|
if let characters = event.charactersIgnoringModifiers {
|
||||||
|
let newGlobalKeybind = GlobalKeybindPreference.init(
|
||||||
|
function: event.modifierFlags.contains(.function),
|
||||||
|
control: event.modifierFlags.contains(.control),
|
||||||
|
command: event.modifierFlags.contains(.command),
|
||||||
|
shift: event.modifierFlags.contains(.shift),
|
||||||
|
option: event.modifierFlags.contains(.option),
|
||||||
|
capsLock: event.modifierFlags.contains(.capsLock),
|
||||||
|
carbonFlags: event.modifierFlags.carbonFlags,
|
||||||
|
characters: characters,
|
||||||
|
keyCode: UInt32(event.keyCode)
|
||||||
|
)
|
||||||
|
|
||||||
|
Preferences.update(.globalHotkey, value: newGlobalKeybind.toJson())
|
||||||
|
|
||||||
|
updateKeybindButton(newGlobalKeybind)
|
||||||
|
buttonClearShortcut.isEnabled = true
|
||||||
|
|
||||||
|
App.shared.shortcutHotkey = HotKey(
|
||||||
|
keyCombo: KeyCombo(
|
||||||
|
carbonKeyCode: UInt32(event.keyCode),
|
||||||
|
carbonModifiers: event.modifierFlags.carbonFlags
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@IBAction func register(_ sender: Any) {
|
||||||
|
unregister(nil)
|
||||||
|
listeningForGlobalHotkey = true
|
||||||
|
view.window?.makeFirstResponder(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
@IBAction func unregister(_ sender: Any?) {
|
||||||
|
listeningForGlobalHotkey = false
|
||||||
|
App.shared.shortcutHotkey = nil
|
||||||
|
buttonSetShortcut.title = ""
|
||||||
|
|
||||||
|
Preferences.update(.globalHotkey, value: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateClearButton(_ globalKeybindPreference: GlobalKeybindPreference?) {
|
||||||
|
if globalKeybindPreference != nil {
|
||||||
|
buttonClearShortcut.isEnabled = true
|
||||||
|
} else {
|
||||||
|
buttonClearShortcut.isEnabled = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateKeybindButton(_ globalKeybindPreference: GlobalKeybindPreference) {
|
||||||
|
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) {
|
||||||
|
self.view.window?.windowController?.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Deinitialization
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
print("VC deallocated")
|
||||||
|
}
|
||||||
|
}
|
36
phpmon/Domain/Preferences/PrefsWC.swift
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
//
|
||||||
|
// PrefsWC.swift
|
||||||
|
// PHP Monitor
|
||||||
|
//
|
||||||
|
// Created by Nico Verbruggen on 02/04/2021.
|
||||||
|
// Copyright © 2021 Nico Verbruggen. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Cocoa
|
||||||
|
|
||||||
|
struct Keys {
|
||||||
|
static let Escape = 53
|
||||||
|
static let Space = 49
|
||||||
|
}
|
||||||
|
|
||||||
|
class PrefsWC: NSWindowController {
|
||||||
|
override func windowDidLoad() {
|
||||||
|
super.windowDidLoad()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func keyDown(with event: NSEvent) {
|
||||||
|
super.keyDown(with: event)
|
||||||
|
|
||||||
|
if let vc = self.contentViewController as? PrefsVC {
|
||||||
|
if vc.listeningForGlobalHotkey {
|
||||||
|
if event.keyCode == Keys.Escape || event.keyCode == Keys.Space {
|
||||||
|
print("A blacklisted key was pressed, canceling listen")
|
||||||
|
vc.listeningForGlobalHotkey = false
|
||||||
|
} else {
|
||||||
|
vc.updateGlobalShortcut(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -15,31 +15,52 @@ class Actions {
|
|||||||
public static func detectPhpVersions() -> [String]
|
public static func detectPhpVersions() -> [String]
|
||||||
{
|
{
|
||||||
let files = Shell.pipe("ls \(Paths.optPath) | grep php@")
|
let files = Shell.pipe("ls \(Paths.optPath) | grep php@")
|
||||||
var versions = files.components(separatedBy: "\n")
|
var versionsOnly = Self.extractPhpVersions(from: files.components(separatedBy: "\n"))
|
||||||
|
|
||||||
// Remove all empty strings
|
|
||||||
versions.removeAll { (string) -> Bool in
|
|
||||||
return (string == "")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get a list of versions only
|
|
||||||
var versionsOnly : [String] = []
|
|
||||||
versions.forEach { (string) in
|
|
||||||
versionsOnly.append(string.components(separatedBy: "php@")[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure the aliased version is detected
|
// Make sure the aliased version is detected
|
||||||
// The user may have `php` installed, but not e.g. `php@8.0`
|
// The user may have `php` installed, but not e.g. `php@8.0`
|
||||||
// We should also detect that as a version that is installed
|
// We should also detect that as a version that is installed
|
||||||
let phpAlias = App.shared.brewPhpVersion
|
let phpAlias = App.shared.brewPhpVersion
|
||||||
|
|
||||||
|
// Avoid inserting a duplicate
|
||||||
if (!versionsOnly.contains(phpAlias)) {
|
if (!versionsOnly.contains(phpAlias)) {
|
||||||
versionsOnly.append(phpAlias);
|
versionsOnly.append(phpAlias);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
print("The PHP versions that were detected are: \(versionsOnly)")
|
||||||
|
|
||||||
return versionsOnly
|
return versionsOnly
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Extracts valid PHP versions from an array of strings.
|
||||||
|
This array of strings is usually retrieved from `grep`.
|
||||||
|
*/
|
||||||
|
public static func extractPhpVersions(
|
||||||
|
from versions: [String],
|
||||||
|
checkBinaries: Bool = true
|
||||||
|
) -> [String] {
|
||||||
|
var output : [String] = []
|
||||||
|
|
||||||
|
versions.filter { (version) -> Bool in
|
||||||
|
// Omit everything that doesn't start with php@
|
||||||
|
// (e.g. something-php@8.0 won't be detected)
|
||||||
|
return version.starts(with: "php@")
|
||||||
|
}.forEach { (string) in
|
||||||
|
let version = string.components(separatedBy: "php@")[1]
|
||||||
|
// Only append the version if it doesn't already exist (avoid dupes),
|
||||||
|
// is supported and where the binary exists (avoids broken installs)
|
||||||
|
if !output.contains(version)
|
||||||
|
&& Constants.SupportedPhpVersions.contains(version)
|
||||||
|
&& (checkBinaries ? Shell.fileExists("\(Paths.optPath)/php@\(version)/bin/php") : true)
|
||||||
|
{
|
||||||
|
output.append(version)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Services
|
// MARK: - Services
|
||||||
|
|
||||||
public static func restartPhpFpm()
|
public static func restartPhpFpm()
|
||||||
@@ -57,6 +78,13 @@ class Actions {
|
|||||||
brew("services restart dnsmasq", sudo: true)
|
brew("services restart dnsmasq", sudo: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static func stopAllServices()
|
||||||
|
{
|
||||||
|
brew("services stop \(App.phpInstall!.formula)", sudo: true)
|
||||||
|
brew("services stop nginx", sudo: true)
|
||||||
|
brew("services stop dnsmasq", sudo: true)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Switching to a new PHP version involves:
|
Switching to a new PHP version involves:
|
||||||
- unlinking the current version
|
- unlinking the current version
|
||||||
@@ -66,17 +94,40 @@ class Actions {
|
|||||||
Please note that depending on which version is installed,
|
Please note that depending on which version is installed,
|
||||||
the version that is switched to may or may not be identical to `php` (without @version).
|
the version that is switched to may or may not be identical to `php` (without @version).
|
||||||
*/
|
*/
|
||||||
public static func switchToPhpVersion(version: String, availableVersions: [String])
|
public static func switchToPhpVersion(
|
||||||
{
|
version: String,
|
||||||
|
availableVersions: [String],
|
||||||
|
completed: @escaping () -> Void
|
||||||
|
) {
|
||||||
|
print("Switching to \(version), unlinking all versions...")
|
||||||
|
|
||||||
|
let group = DispatchGroup()
|
||||||
|
|
||||||
availableVersions.forEach { (available) in
|
availableVersions.forEach { (available) in
|
||||||
let formula = (available == App.shared.brewPhpVersion) ? "php" : "php@\(available)"
|
group.enter()
|
||||||
brew("unlink \(formula)")
|
|
||||||
brew("services stop \(formula)", sudo: true)
|
DispatchQueue.global(qos: .userInitiated).async {
|
||||||
|
let formula = (available == App.shared.brewPhpVersion)
|
||||||
|
? "php" : "php@\(available)"
|
||||||
|
|
||||||
|
brew("unlink \(formula)")
|
||||||
|
brew("services stop \(formula)", sudo: true)
|
||||||
|
|
||||||
|
group.leave()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let formula = (version == App.shared.brewPhpVersion) ? "php" : "php@\(version)"
|
group.notify(queue: .global(qos: .userInitiated)) {
|
||||||
brew("link \(formula) --overwrite --force")
|
print("All versions have been unlinked!")
|
||||||
brew("services start \(formula)", sudo: true)
|
print("Linking the new version!")
|
||||||
|
|
||||||
|
let formula = (version == App.shared.brewPhpVersion) ? "php" : "php@\(version)"
|
||||||
|
brew("link \(formula) --overwrite --force")
|
||||||
|
brew("services start \(formula)", sudo: true)
|
||||||
|
|
||||||
|
print("The new version has been linked!")
|
||||||
|
completed()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Finding Config Files
|
// MARK: - Finding Config Files
|
||||||
@@ -87,6 +138,13 @@ class Actions {
|
|||||||
NSWorkspace.shared.activateFileViewerSelecting(files as [URL])
|
NSWorkspace.shared.activateFileViewerSelecting(files as [URL])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static func openGlobalComposerFolder()
|
||||||
|
{
|
||||||
|
let file = FileManager.default.homeDirectoryForCurrentUser
|
||||||
|
.appendingPathComponent(".composer/composer.json")
|
||||||
|
NSWorkspace.shared.activateFileViewerSelecting([file] as [URL])
|
||||||
|
}
|
||||||
|
|
||||||
public static func openPhpConfigFolder(version: String)
|
public static func openPhpConfigFolder(version: String)
|
||||||
{
|
{
|
||||||
let files = [NSURL(fileURLWithPath: "\(Paths.etcPath)/php/\(version)/php.ini")];
|
let files = [NSURL(fileURLWithPath: "\(Paths.etcPath)/php/\(version)/php.ini")];
|
||||||
@@ -95,8 +153,9 @@ class Actions {
|
|||||||
|
|
||||||
public static func openValetConfigFolder()
|
public static func openValetConfigFolder()
|
||||||
{
|
{
|
||||||
let files = [NSURL(fileURLWithPath: NSString(string: "~/.config/valet").expandingTildeInPath)];
|
let file = FileManager.default.homeDirectoryForCurrentUser
|
||||||
NSWorkspace.shared.activateFileViewerSelecting(files as [URL])
|
.appendingPathComponent(".config/valet")
|
||||||
|
NSWorkspace.shared.activateFileViewerSelecting([file] as [URL])
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Quick Fix
|
// MARK: - Quick Fix
|
||||||
@@ -110,7 +169,7 @@ class Actions {
|
|||||||
{
|
{
|
||||||
brew("services restart dnsmasq", sudo: true)
|
brew("services restart dnsmasq", sudo: true)
|
||||||
|
|
||||||
self.detectPhpVersions().forEach { (version) in
|
detectPhpVersions().forEach { (version) in
|
||||||
let formula = (version == App.shared.brewPhpVersion) ? "php" : "php@\(version)"
|
let formula = (version == App.shared.brewPhpVersion) ? "php" : "php@\(version)"
|
||||||
brew("unlink php@\(version)")
|
brew("unlink php@\(version)")
|
||||||
brew("services stop \(formula)")
|
brew("services stop \(formula)")
|
||||||
@@ -140,7 +199,11 @@ class Actions {
|
|||||||
*/
|
*/
|
||||||
public static func sed(file: String, original: String, replacement: String)
|
public static func sed(file: String, original: String, replacement: String)
|
||||||
{
|
{
|
||||||
Shell.run("sed -i '' 's/\(original)/\(replacement)/g' \(file)")
|
// Escape slashes (or `sed` won't work)
|
||||||
|
let e_original = original.replacingOccurrences(of: "/", with: "\\/")
|
||||||
|
let e_replacment = replacement.replacingOccurrences(of: "/", with: "\\/")
|
||||||
|
|
||||||
|
Shell.run("sed -i '' 's/\(e_original)/\(e_replacment)/g' \(file)")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -14,8 +14,9 @@ class Command {
|
|||||||
|
|
||||||
- Parameter path: The path of the command or program to invoke.
|
- Parameter path: The path of the command or program to invoke.
|
||||||
- Parameter arguments: A list of arguments that are passed on.
|
- Parameter arguments: A list of arguments that are passed on.
|
||||||
|
- Parameter trimNewlines: Removes empty new line output.
|
||||||
*/
|
*/
|
||||||
public static func execute(path: String, arguments: [String]) -> String {
|
public static func execute(path: String, arguments: [String], trimNewlines: Bool = false) -> String {
|
||||||
let task = Process()
|
let task = Process()
|
||||||
task.launchPath = path
|
task.launchPath = path
|
||||||
task.arguments = arguments
|
task.arguments = arguments
|
||||||
@@ -26,6 +27,13 @@ class Command {
|
|||||||
|
|
||||||
let data = pipe.fileHandleForReading.readDataToEndOfFile()
|
let data = pipe.fileHandleForReading.readDataToEndOfFile()
|
||||||
let output: String = String.init(data: data, encoding: String.Encoding.utf8)!
|
let output: String = String.init(data: data, encoding: String.Encoding.utf8)!
|
||||||
|
|
||||||
|
if (trimNewlines) {
|
||||||
|
return output.components(separatedBy: .newlines)
|
||||||
|
.filter({ !$0.isEmpty })
|
||||||
|
.joined(separator: "\n")
|
||||||
|
}
|
||||||
|
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -23,41 +23,45 @@ class Paths {
|
|||||||
|
|
||||||
if (optBrewFound) {
|
if (optBrewFound) {
|
||||||
// This is usually the case with Homebrew installed on Apple Silicon
|
// This is usually the case with Homebrew installed on Apple Silicon
|
||||||
self.baseDir = .opt
|
baseDir = .opt
|
||||||
} else if (usrBrewFound) {
|
} else if (usrBrewFound) {
|
||||||
// This is usually the case with Homebrew installed on Intel (or Rosetta 2)
|
// This is usually the case with Homebrew installed on Intel (or Rosetta 2)
|
||||||
self.baseDir = .usr
|
baseDir = .usr
|
||||||
} else {
|
} else {
|
||||||
// Falling back to default "legacy" Homebrew location (for Intel)
|
// Falling back to default "legacy" Homebrew location (for Intel)
|
||||||
print("Seems like we couldn't determine the Homebrew directory.")
|
print("Seems like we couldn't determine the Homebrew directory.")
|
||||||
print("This usually means we're in trouble... (no Homebrew?)")
|
print("This usually means we're in trouble... (no Homebrew?)")
|
||||||
self.baseDir = .usr
|
baseDir = .usr
|
||||||
}
|
}
|
||||||
|
|
||||||
print("Homebrew directory: \(self.baseDir)")
|
print("Homebrew directory: \(baseDir)")
|
||||||
}
|
}
|
||||||
|
|
||||||
// - MARK: Binaries
|
// - MARK: Binaries
|
||||||
|
|
||||||
public static var brew: String {
|
public static var brew: String {
|
||||||
return "\(self.binPath)/brew"
|
return "\(binPath)/brew"
|
||||||
}
|
}
|
||||||
|
|
||||||
public static var php: String {
|
public static var php: String {
|
||||||
return "\(self.binPath)/php"
|
return "\(binPath)/php"
|
||||||
|
}
|
||||||
|
|
||||||
|
public static var phpConfig: String {
|
||||||
|
return "\(binPath)/php-config"
|
||||||
}
|
}
|
||||||
|
|
||||||
// - MARK: Paths
|
// - MARK: Paths
|
||||||
|
|
||||||
public static var binPath: String {
|
public static var binPath: String {
|
||||||
return "\(self.shared.baseDir.rawValue)/bin"
|
return "\(shared.baseDir.rawValue)/bin"
|
||||||
}
|
}
|
||||||
|
|
||||||
public static var optPath: String {
|
public static var optPath: String {
|
||||||
return "\(self.shared.baseDir.rawValue)/opt"
|
return "\(shared.baseDir.rawValue)/opt"
|
||||||
}
|
}
|
||||||
|
|
||||||
public static var etcPath: String {
|
public static var etcPath: String {
|
||||||
return "\(self.shared.baseDir.rawValue)/etc"
|
return "\(shared.baseDir.rawValue)/etc"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -16,7 +16,7 @@ class Shell {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static func pipe(_ command: String) -> String {
|
public static func pipe(_ command: String) -> String {
|
||||||
Shell.user.pipe(command)
|
return Shell.user.pipe(command)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Singleton
|
// MARK: - Singleton
|
||||||
@@ -29,7 +29,7 @@ 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
|
||||||
self.shell = at_least_10_15 ? "/bin/sh" : "/bin/bash"
|
shell = at_least_10_15 ? "/bin/sh" : "/bin/bash"
|
||||||
print(at_least_10_15 ? "Detected recent macOS (> 10.15): defaulting to /bin/sh"
|
print(at_least_10_15 ? "Detected recent macOS (> 10.15): defaulting to /bin/sh"
|
||||||
: "Detected older macOS (< 10.15): so defaulting to /bin/bash")
|
: "Detected older macOS (< 10.15): so defaulting to /bin/bash")
|
||||||
}
|
}
|
||||||
@@ -47,7 +47,7 @@ class Shell {
|
|||||||
*/
|
*/
|
||||||
func run(_ command: String) {
|
func run(_ command: String) {
|
||||||
// Equivalent of piping to /dev/null; don't do anything with the string
|
// Equivalent of piping to /dev/null; don't do anything with the string
|
||||||
_ = self.pipe(command)
|
_ = pipe(command)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -21,57 +21,61 @@ class Startup {
|
|||||||
*/
|
*/
|
||||||
func checkEnvironment(success: () -> Void, failure: @escaping () -> Void)
|
func checkEnvironment(success: () -> Void, failure: @escaping () -> Void)
|
||||||
{
|
{
|
||||||
self.failureCallback = failure
|
failureCallback = failure
|
||||||
|
|
||||||
self.performEnvironmentCheck(
|
performEnvironmentCheck(
|
||||||
!Shell.fileExists("\(Paths.binPath)/php"),
|
!Shell.fileExists("\(Paths.binPath)/php"),
|
||||||
messageText: "startup.errors.php_binary.title".localized,
|
messageText: "startup.errors.php_binary.title".localized,
|
||||||
informativeText: "startup.errors.php_binary_desc".localized,
|
informativeText: "startup.errors.php_binary_desc".localized,
|
||||||
breaking: true
|
breaking: true
|
||||||
)
|
)
|
||||||
|
|
||||||
self.performEnvironmentCheck(
|
performEnvironmentCheck(
|
||||||
!Shell.pipe("ls \(Paths.optPath) | grep php").contains("php"),
|
!Shell.pipe("ls \(Paths.optPath) | grep php").contains("php"),
|
||||||
messageText: "startup.errors.php_opt.title".localized,
|
messageText: "startup.errors.php_opt.title".localized,
|
||||||
informativeText: "startup.errors.php_opt.desc".localized,
|
informativeText: "startup.errors.php_opt.desc".localized,
|
||||||
breaking: true
|
breaking: true
|
||||||
)
|
)
|
||||||
|
|
||||||
self.performEnvironmentCheck(
|
performEnvironmentCheck(
|
||||||
// Older versions of Valet might be located in `/usr/local/bin` regardless of Homebrew prefix
|
// Check for Valet; it can be symlinked or in .composer/vendor/bin
|
||||||
!(Shell.pipe("which valet").contains("/usr/local/bin/valet")
|
!(Shell.fileExists("/usr/local/bin/valet")
|
||||||
|| Shell.pipe("which valet").contains("/opt/homebrew/bin/valet")),
|
|| Shell.fileExists("/opt/homebrew/bin/valet")
|
||||||
|
|| Shell.fileExists("~/.composer/vendor/bin/valet")
|
||||||
|
),
|
||||||
messageText: "startup.errors.valet_executable.title".localized,
|
messageText: "startup.errors.valet_executable.title".localized,
|
||||||
informativeText: "startup.errors.valet_executable.desc".localized,
|
informativeText: "startup.errors.valet_executable.desc".localized,
|
||||||
breaking: true
|
breaking: true
|
||||||
)
|
)
|
||||||
|
|
||||||
self.performEnvironmentCheck(
|
performEnvironmentCheck(
|
||||||
!Shell.pipe("cat /private/etc/sudoers.d/brew").contains("\(Paths.binPath)/brew"),
|
!Shell.pipe("cat /private/etc/sudoers.d/brew").contains("\(Paths.binPath)/brew"),
|
||||||
messageText: "startup.errors.sudoers_brew.title".localized,
|
messageText: "startup.errors.sudoers_brew.title".localized,
|
||||||
informativeText: "startup.errors.sudoers_brew.desc".localized,
|
informativeText: "startup.errors.sudoers_brew.desc".localized,
|
||||||
breaking: true
|
breaking: true
|
||||||
)
|
)
|
||||||
|
|
||||||
self.performEnvironmentCheck(
|
performEnvironmentCheck(
|
||||||
// Older versions of Valet might be located in `/usr/local/bin` regardless of Homebrew prefix
|
// Check for Valet; it can be symlinked or in .composer/vendor/bin
|
||||||
!(Shell.pipe("cat /private/etc/sudoers.d/valet").contains("/usr/local/bin/valet")
|
!(Shell.pipe("cat /private/etc/sudoers.d/valet").contains("/usr/local/bin/valet")
|
||||||
|| Shell.pipe("cat /private/etc/sudoers.d/valet").contains("/opt/homebrew/bin/valet")),
|
|| Shell.pipe("cat /private/etc/sudoers.d/valet").contains("/opt/homebrew/bin/valet")
|
||||||
|
|| Shell.pipe("cat /private/etc/sudoers.d/valet").contains(".composer/vendor/bin/valet")
|
||||||
|
),
|
||||||
messageText: "startup.errors.sudoers_valet.title".localized,
|
messageText: "startup.errors.sudoers_valet.title".localized,
|
||||||
informativeText: "startup.errors.sudoers_valet.desc".localized,
|
informativeText: "startup.errors.sudoers_valet.desc".localized,
|
||||||
breaking: true
|
breaking: true
|
||||||
)
|
)
|
||||||
|
|
||||||
let services = Shell.pipe("\(Paths.brew) services list | grep php")
|
let services = Shell.pipe("\(Paths.brew) services list | grep php")
|
||||||
self.performEnvironmentCheck(
|
performEnvironmentCheck(
|
||||||
(services.countInstances(of: "started") > 1),
|
(services.countInstances(of: "started") > 1),
|
||||||
messageText: "startup.errors.services.title".localized,
|
messageText: "startup.errors.services.title".localized,
|
||||||
informativeText: "startup.errors.services.desc".localized,
|
informativeText: "startup.errors.services.desc".localized,
|
||||||
breaking: false
|
breaking: false
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!self.failed) {
|
if (!failed) {
|
||||||
self.determineBrewAliasVersion()
|
determineBrewAliasVersion()
|
||||||
success()
|
success()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -111,13 +115,13 @@ class Startup {
|
|||||||
) {
|
) {
|
||||||
if (!condition) { return }
|
if (!condition) { return }
|
||||||
|
|
||||||
self.failed = breaking
|
failed = breaking
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
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)
|
||||||
// Only breaking issues will throw the extra retry modal
|
// Only breaking issues will throw the extra retry modal
|
||||||
breaking ? self.failureCallback() : ()
|
breaking ? failureCallback() : ()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -22,9 +22,11 @@
|
|||||||
"mi_restart_php_fpm" = "Restart service: php";
|
"mi_restart_php_fpm" = "Restart service: php";
|
||||||
"mi_restart_nginx" = "Restart service: nginx";
|
"mi_restart_nginx" = "Restart service: nginx";
|
||||||
"mi_restart_dnsmasq" = "Restart service: dnsmasq";
|
"mi_restart_dnsmasq" = "Restart service: dnsmasq";
|
||||||
"mi_restart_specific" = "Restart specific service";
|
"mi_manage_services" = "Manage services";
|
||||||
"mi_restart_all_services" = "Restart all services";
|
"mi_restart_all_services" = "Restart all services";
|
||||||
|
"mi_stop_all_services" = "Stop all services";
|
||||||
"mi_force_load_latest" = "Force load latest PHP version";
|
"mi_force_load_latest" = "Force load latest PHP version";
|
||||||
|
"mi_php_refresh" = "Refresh information";
|
||||||
|
|
||||||
"mi_configuration" = "Configuration";
|
"mi_configuration" = "Configuration";
|
||||||
"mi_limits" = "Limits Configuration";
|
"mi_limits" = "Limits Configuration";
|
||||||
@@ -34,18 +36,42 @@
|
|||||||
|
|
||||||
"mi_valet_config" = "Locate Valet folder (.config/valet)";
|
"mi_valet_config" = "Locate Valet folder (.config/valet)";
|
||||||
"mi_php_config" = "Locate PHP configuration file (php.ini)";
|
"mi_php_config" = "Locate PHP configuration file (php.ini)";
|
||||||
|
"mi_global_composer" = "Locate global composer.json file (.composer)";
|
||||||
"mi_phpinfo" = "Show current configuration (phpinfo)";
|
"mi_phpinfo" = "Show current configuration (phpinfo)";
|
||||||
"mi_detected_extensions" = "Detected Extensions";
|
"mi_detected_extensions" = "Detected Extensions";
|
||||||
"mi_no_extensions_detected" = "No additional extensions detected.";
|
"mi_no_extensions_detected" = "No additional extensions detected.";
|
||||||
|
|
||||||
|
"mi_preferences" = "Preferences...";
|
||||||
"mi_quit" = "Quit PHP Monitor";
|
"mi_quit" = "Quit PHP Monitor";
|
||||||
"mi_about" = "About PHP Monitor";
|
"mi_about" = "About PHP Monitor";
|
||||||
|
|
||||||
|
// PREFERENCES
|
||||||
|
|
||||||
|
"prefs.title" = "PHP Monitor";
|
||||||
|
"prefs.close" = "Close";
|
||||||
|
|
||||||
|
"prefs.global_shortcut" = "Global shortcut:";
|
||||||
|
"prefs.dynamic_icon" = "Dynamic icon:";
|
||||||
|
|
||||||
|
"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.shortcut_set" = "Set global shortcut";
|
||||||
|
"prefs.shortcut_listening" = "<listening for keypress>";
|
||||||
|
"prefs.shortcut_clear" = "Clear";
|
||||||
|
"prefs.shortcut_desc" = "If a shortcut combination is set up, you can toggle PHP Monitor\nwherever you are by pressing the key combination you chose.\n(Cancel choosing a shortcut by pressing the spacebar.)";
|
||||||
|
|
||||||
// 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 finished the switch to PHP %@.";
|
||||||
|
|
||||||
|
"notification.services_stopped" = "Valet services stopped";
|
||||||
|
"notification.services_stopped_desc" = "All services have been successfully stopped.";
|
||||||
|
|
||||||
|
"notification.services_restarted" = "Valet services restarted";
|
||||||
|
"notification.services_restarted_desc" = "All services have been successfully restarted.";
|
||||||
|
|
||||||
// ALERTS
|
// ALERTS
|
||||||
|
|
||||||
// Force Reload Started
|
// Force Reload Started
|
||||||
@@ -56,6 +82,10 @@
|
|||||||
"alert.force_reload_done.title" = "PHP has been force reloaded";
|
"alert.force_reload_done.title" = "PHP has been force reloaded";
|
||||||
"alert.force_reload_done.info" = "All appropriate services have been restarted, and the latest version of PHP is now active. You can now try switching to another version of PHP. If visiting sites still does not work, you may try running `valet install` again, this can fix a 502 issue (Bad Gateway).";
|
"alert.force_reload_done.info" = "All appropriate services have been restarted, and the latest version of PHP is now active. You can now try switching to another version of PHP. If visiting sites still does not work, you may try running `valet install` again, this can fix a 502 issue (Bad Gateway).";
|
||||||
|
|
||||||
|
// PHP FPM Broken
|
||||||
|
"alert.php_fpm_broken.title" = "PHP-FPM configuration is incorrect";
|
||||||
|
"alert.php_fpm_broken.info" = "PHP Monitor has determined that there are issues with your PHP-FPM config: it's not pointing to the Valet socket. This will result in 502 Bad Gateway if you visit websites linked via Valet.\n\nYou can usually fix this by running\n`valet install`, which updates your\n PHP-FPM configuration.";
|
||||||
|
|
||||||
// PHP Monitor Cannot Start
|
// PHP Monitor Cannot Start
|
||||||
"alert.cannot_start.title" = "PHP Monitor cannot start";
|
"alert.cannot_start.title" = "PHP Monitor cannot start";
|
||||||
"alert.cannot_start.info" = "The issue you were just notified about is keeping PHP Monitor from functioning correctly. Please fix the issue and restart PHP Monitor. After clicking on OK, PHP Monitor will close.\n\nIf you have fixed the issue (or don't remember what the exact issue is) you can click on Retry, which will have PHP Monitor retry the startup checks.";
|
"alert.cannot_start.info" = "The issue you were just notified about is keeping PHP Monitor from functioning correctly. Please fix the issue and restart PHP Monitor. After clicking on OK, PHP Monitor will close.\n\nIf you have fixed the issue (or don't remember what the exact issue is) you can click on Retry, which will have PHP Monitor retry the startup checks.";
|
||||||
@@ -74,7 +104,7 @@
|
|||||||
|
|
||||||
/// 3. Valet not installed
|
/// 3. Valet not installed
|
||||||
"startup.errors.valet_executable.title" = "Laravel Valet is not correctly installed";
|
"startup.errors.valet_executable.title" = "Laravel Valet is not correctly installed";
|
||||||
"startup.errors.valet_executable.desc" = "You must install Valet with composer. Try running `which valet` in Terminal, it should return `/usr/local/bin/valet` or `/opt/homebrew/bin/valet`. The app will not work correctly until you resolve this issue.";
|
"startup.errors.valet_executable.desc" = "You must install Valet with composer. Try running `which valet` in Terminal, it should return `/usr/local/bin/valet` or `/opt/homebrew/bin/valet`. The app will not work correctly until you resolve this issue. (PHP Monitor checks for the existence of `valet` in either of these paths.)";
|
||||||
|
|
||||||
/// 4. Brew & sudoers
|
/// 4. Brew & sudoers
|
||||||
"startup.errors.sudoers_brew.title" = "Brew has not been added to sudoers.d";
|
"startup.errors.sudoers_brew.title" = "Brew has not been added to sudoers.d";
|
||||||
|