mirror of
https://github.com/nicoverbruggen/phpmon.git
synced 2025-08-09 04:42:59 +02:00
Compare commits
36 Commits
Author | SHA1 | Date | |
---|---|---|---|
015f406ddf | |||
e1a97672b5 | |||
493b5945f9 | |||
52606aae8b | |||
2d6ca0f841 | |||
34900f929f | |||
5dbd05fdfb | |||
fe3cf9adb1 | |||
9bc8460cce | |||
4cbd2fd6eb | |||
6fef3fe37a | |||
72a20d1ed9 | |||
73ed80434a | |||
a78672927b | |||
4256eae442 | |||
76412b68f3 | |||
9153bb140a | |||
c9c15d10f9 | |||
e8c2277ef5 | |||
23720c5dc9 | |||
f881f07cba | |||
b072ee8dec | |||
acfbc0b66f | |||
c738a03934 | |||
84d62f3583 | |||
f9faa03b92 | |||
55f6c3c6cd | |||
a0c6753761 | |||
327125608a | |||
6c0045302b | |||
9c85bebe72 | |||
fb56cd551e | |||
e83d507e79 | |||
0c0e7fc87d | |||
faf49fbe1d | |||
2925b0ff79 |
@@ -3,12 +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 */; };
|
5420395926135DC100FB00FA /* PrefsVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5420395826135DC100FB00FA /* PrefsVC.swift */; };
|
||||||
5420395F2613607600FB00FA /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5420395E2613607600FB00FA /* Preferences.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,10 +18,13 @@
|
|||||||
C41C1B3E22B0098000E7CF16 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C41C1B3C22B0098000E7CF16 /* Main.storyboard */; };
|
C41C1B3E22B0098000E7CF16 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C41C1B3C22B0098000E7CF16 /* Main.storyboard */; };
|
||||||
C41C1B4722B009A400E7CF16 /* Shell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4622B009A400E7CF16 /* Shell.swift */; };
|
C41C1B4722B009A400E7CF16 /* Shell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4622B009A400E7CF16 /* Shell.swift */; };
|
||||||
C41C1B4922B00A9800E7CF16 /* MenuBarImageGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4822B00A9800E7CF16 /* MenuBarImageGenerator.swift */; };
|
C41C1B4922B00A9800E7CF16 /* MenuBarImageGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4822B00A9800E7CF16 /* MenuBarImageGenerator.swift */; };
|
||||||
C41C1B4B22B019FF00E7CF16 /* PhpInstallation.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4A22B019FF00E7CF16 /* PhpInstallation.swift */; };
|
C41C1B4B22B019FF00E7CF16 /* ActivePhpInstallation.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4A22B019FF00E7CF16 /* ActivePhpInstallation.swift */; };
|
||||||
C41C1B4D22B0215A00E7CF16 /* Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4C22B0215A00E7CF16 /* Actions.swift */; };
|
C41C1B4D22B0215A00E7CF16 /* Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4C22B0215A00E7CF16 /* Actions.swift */; };
|
||||||
|
C41CD0292628D8EE0065BBED /* GlobalKeybindPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41CD0282628D8EE0065BBED /* GlobalKeybindPreference.swift */; };
|
||||||
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 */; };
|
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 */; };
|
||||||
@@ -38,10 +42,17 @@
|
|||||||
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 */; };
|
||||||
C4EE188422D3386B00E126E5 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EE188322D3386B00E126E5 /* Constants.swift */; };
|
C4EE188422D3386B00E126E5 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EE188322D3386B00E126E5 /* Constants.swift */; };
|
||||||
|
C4F2E4372752F0870020E974 /* HomebrewDiagnostics.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F2E4362752F0870020E974 /* HomebrewDiagnostics.swift */; };
|
||||||
|
C4F2E4382752F08D0020E974 /* HomebrewDiagnostics.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F2E4362752F0870020E974 /* HomebrewDiagnostics.swift */; };
|
||||||
|
C4F2E43A2752F7D00020E974 /* PhpInstallation.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F2E4392752F7D00020E974 /* PhpInstallation.swift */; };
|
||||||
|
C4F2E43B27530F750020E974 /* PhpInstallation.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F2E4392752F7D00020E974 /* PhpInstallation.swift */; };
|
||||||
C4F7809625D7FBF8000DBC97 /* Shell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4622B009A400E7CF16 /* Shell.swift */; };
|
C4F7809625D7FBF8000DBC97 /* Shell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4622B009A400E7CF16 /* Shell.swift */; };
|
||||||
C4F7809C25D80344000DBC97 /* CommandTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F7809B25D80344000DBC97 /* CommandTest.swift */; };
|
C4F7809C25D80344000DBC97 /* CommandTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F7809B25D80344000DBC97 /* CommandTest.swift */; };
|
||||||
C4F7809F25D8037C000DBC97 /* Command.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42295DC2358D02000E263B2 /* Command.swift */; };
|
C4F7809F25D8037C000DBC97 /* Command.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42295DC2358D02000E263B2 /* Command.swift */; };
|
||||||
@@ -63,7 +74,7 @@
|
|||||||
C4F780C925D80B75000DBC97 /* StringExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46FA23E246C358E00944F05 /* StringExtension.swift */; };
|
C4F780C925D80B75000DBC97 /* StringExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46FA23E246C358E00944F05 /* StringExtension.swift */; };
|
||||||
C4F780CA25D80B75000DBC97 /* HomebrewPackage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C412E5FB25700D5300A1FB67 /* HomebrewPackage.swift */; };
|
C4F780CA25D80B75000DBC97 /* HomebrewPackage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C412E5FB25700D5300A1FB67 /* HomebrewPackage.swift */; };
|
||||||
C4F780CB25D80B75000DBC97 /* StatsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48D0CA225CC992000CC7490 /* StatsView.swift */; };
|
C4F780CB25D80B75000DBC97 /* StatsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48D0CA225CC992000CC7490 /* StatsView.swift */; };
|
||||||
C4F780CC25D80B75000DBC97 /* PhpInstallation.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4A22B019FF00E7CF16 /* PhpInstallation.swift */; };
|
C4F780CC25D80B75000DBC97 /* ActivePhpInstallation.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4A22B019FF00E7CF16 /* ActivePhpInstallation.swift */; };
|
||||||
C4F780CD25D80B75000DBC97 /* Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = C476FF9722B0DD830098105B /* Alert.swift */; };
|
C4F780CD25D80B75000DBC97 /* Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = C476FF9722B0DD830098105B /* Alert.swift */; };
|
||||||
C4F780CE25D80B75000DBC97 /* LocalNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = C474B00524C0E98C00066A22 /* LocalNotification.swift */; };
|
C4F780CE25D80B75000DBC97 /* LocalNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = C474B00524C0E98C00066A22 /* LocalNotification.swift */; };
|
||||||
C4F8C0A422D4F12C002EFE61 /* DateExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F8C0A322D4F12C002EFE61 /* DateExtension.swift */; };
|
C4F8C0A422D4F12C002EFE61 /* DateExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F8C0A322D4F12C002EFE61 /* DateExtension.swift */; };
|
||||||
@@ -94,10 +105,12 @@
|
|||||||
C41C1B4022B0098000E7CF16 /* phpmon.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = phpmon.entitlements; sourceTree = "<group>"; };
|
C41C1B4022B0098000E7CF16 /* phpmon.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = phpmon.entitlements; sourceTree = "<group>"; };
|
||||||
C41C1B4622B009A400E7CF16 /* Shell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shell.swift; sourceTree = "<group>"; };
|
C41C1B4622B009A400E7CF16 /* Shell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shell.swift; sourceTree = "<group>"; };
|
||||||
C41C1B4822B00A9800E7CF16 /* MenuBarImageGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuBarImageGenerator.swift; sourceTree = "<group>"; };
|
C41C1B4822B00A9800E7CF16 /* MenuBarImageGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuBarImageGenerator.swift; sourceTree = "<group>"; };
|
||||||
C41C1B4A22B019FF00E7CF16 /* PhpInstallation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpInstallation.swift; sourceTree = "<group>"; };
|
C41C1B4A22B019FF00E7CF16 /* ActivePhpInstallation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivePhpInstallation.swift; sourceTree = "<group>"; };
|
||||||
C41C1B4C22B0215A00E7CF16 /* Actions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Actions.swift; sourceTree = "<group>"; };
|
C41C1B4C22B0215A00E7CF16 /* Actions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Actions.swift; sourceTree = "<group>"; };
|
||||||
|
C41CD0282628D8EE0065BBED /* GlobalKeybindPreference.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlobalKeybindPreference.swift; sourceTree = "<group>"; };
|
||||||
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>"; };
|
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>"; };
|
||||||
@@ -113,12 +126,15 @@
|
|||||||
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>"; };
|
||||||
C4E713562570150F00007428 /* SECURITY.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = SECURITY.md; sourceTree = "<group>"; };
|
C4E713562570150F00007428 /* SECURITY.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = SECURITY.md; sourceTree = "<group>"; };
|
||||||
C4E713572570151400007428 /* docs */ = {isa = PBXFileReference; lastKnownFileType = folder; path = docs; sourceTree = "<group>"; };
|
C4E713572570151400007428 /* docs */ = {isa = PBXFileReference; lastKnownFileType = folder; path = docs; sourceTree = "<group>"; };
|
||||||
C4EE188322D3386B00E126E5 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = "<group>"; };
|
C4EE188322D3386B00E126E5 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = "<group>"; };
|
||||||
|
C4F2E4362752F0870020E974 /* HomebrewDiagnostics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomebrewDiagnostics.swift; sourceTree = "<group>"; };
|
||||||
|
C4F2E4392752F7D00020E974 /* PhpInstallation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpInstallation.swift; sourceTree = "<group>"; };
|
||||||
C4F7807425D7F7E5000DBC97 /* RELEASE.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = RELEASE.md; sourceTree = "<group>"; };
|
C4F7807425D7F7E5000DBC97 /* RELEASE.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = RELEASE.md; sourceTree = "<group>"; };
|
||||||
C4F7807925D7F84B000DBC97 /* phpmon-tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "phpmon-tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
|
C4F7807925D7F84B000DBC97 /* phpmon-tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "phpmon-tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
C4F7807D25D7F84B000DBC97 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
C4F7807D25D7F84B000DBC97 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
@@ -135,6 +151,7 @@
|
|||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
C4998F0626175E7200B2526E /* HotKey in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -151,12 +168,24 @@
|
|||||||
5420395726135DB800FB00FA /* Preferences */ = {
|
5420395726135DB800FB00FA /* Preferences */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
C4998F092617633900B2526E /* PrefsWC.swift */,
|
||||||
5420395826135DC100FB00FA /* PrefsVC.swift */,
|
5420395826135DC100FB00FA /* PrefsVC.swift */,
|
||||||
5420395E2613607600FB00FA /* Preferences.swift */,
|
5420395E2613607600FB00FA /* Preferences.swift */,
|
||||||
|
C41CD0272628D8E20065BBED /* Keybinds */,
|
||||||
);
|
);
|
||||||
path = Preferences;
|
path = Preferences;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
54B20EDF263AA22C00D3250E /* PHP */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
C41C1B4A22B019FF00E7CF16 /* ActivePhpInstallation.swift */,
|
||||||
|
C4F2E4392752F7D00020E974 /* PhpInstallation.swift */,
|
||||||
|
C4ACA38E25C754C100060C66 /* PhpExtension.swift */,
|
||||||
|
);
|
||||||
|
path = PHP;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
C405A4CD24B9B9070062FAFA /* IAP */ = {
|
C405A4CD24B9B9070062FAFA /* IAP */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -204,13 +233,22 @@
|
|||||||
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 */,
|
|
||||||
C4B13B1D25C4915000548C3A /* Core */,
|
C4B13B1D25C4915000548C3A /* Core */,
|
||||||
|
54B20EDF263AA22C00D3250E /* PHP */,
|
||||||
|
C4F7808A25D7F918000DBC97 /* Terminal */,
|
||||||
C47331A0247093AC009A0597 /* Menu */,
|
C47331A0247093AC009A0597 /* Menu */,
|
||||||
|
5420395726135DB800FB00FA /* Preferences */,
|
||||||
C4811D2822D70D9C00B5F6B3 /* Helpers */,
|
C4811D2822D70D9C00B5F6B3 /* Helpers */,
|
||||||
C4F8C0A222D4F100002EFE61 /* Extensions */,
|
C4F8C0A222D4F100002EFE61 /* Extensions */,
|
||||||
);
|
);
|
||||||
@@ -236,6 +274,8 @@
|
|||||||
C476FF9722B0DD830098105B /* Alert.swift */,
|
C476FF9722B0DD830098105B /* Alert.swift */,
|
||||||
C41C1B4822B00A9800E7CF16 /* MenuBarImageGenerator.swift */,
|
C41C1B4822B00A9800E7CF16 /* MenuBarImageGenerator.swift */,
|
||||||
C474B00524C0E98C00066A22 /* LocalNotification.swift */,
|
C474B00524C0E98C00066A22 /* LocalNotification.swift */,
|
||||||
|
C4F2E4362752F0870020E974 /* HomebrewDiagnostics.swift */,
|
||||||
|
C412E5FB25700D5300A1FB67 /* HomebrewPackage.swift */,
|
||||||
);
|
);
|
||||||
path = Helpers;
|
path = Helpers;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -245,9 +285,8 @@
|
|||||||
children = (
|
children = (
|
||||||
C41C1B3C22B0098000E7CF16 /* Main.storyboard */,
|
C41C1B3C22B0098000E7CF16 /* Main.storyboard */,
|
||||||
C4811D2322D70A4700B5F6B3 /* App.swift */,
|
C4811D2322D70A4700B5F6B3 /* App.swift */,
|
||||||
C41C1B4A22B019FF00E7CF16 /* PhpInstallation.swift */,
|
C4D8016522B1584700C6DA1B /* Startup.swift */,
|
||||||
C4ACA38E25C754C100060C66 /* PhpExtension.swift */,
|
C41C1B4C22B0215A00E7CF16 /* Actions.swift */,
|
||||||
C412E5FB25700D5300A1FB67 /* HomebrewPackage.swift */,
|
|
||||||
);
|
);
|
||||||
path = Core;
|
path = Core;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -273,8 +312,6 @@
|
|||||||
C49EAB45259FC305007F6C3B /* Paths.swift */,
|
C49EAB45259FC305007F6C3B /* Paths.swift */,
|
||||||
C42295DC2358D02000E263B2 /* Command.swift */,
|
C42295DC2358D02000E263B2 /* Command.swift */,
|
||||||
C41C1B4622B009A400E7CF16 /* Shell.swift */,
|
C41C1B4622B009A400E7CF16 /* Shell.swift */,
|
||||||
C4D8016522B1584700C6DA1B /* Startup.swift */,
|
|
||||||
C41C1B4C22B0215A00E7CF16 /* Actions.swift */,
|
|
||||||
);
|
);
|
||||||
path = Terminal;
|
path = Terminal;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -285,6 +322,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>";
|
||||||
@@ -305,6 +343,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";
|
||||||
@@ -355,6 +396,9 @@
|
|||||||
Base,
|
Base,
|
||||||
);
|
);
|
||||||
mainGroup = C41C1B2A22B0097F00E7CF16;
|
mainGroup = C41C1B2A22B0097F00E7CF16;
|
||||||
|
packageReferences = (
|
||||||
|
C4998F0426175E7200B2526E /* XCRemoteSwiftPackageReference "HotKey" */,
|
||||||
|
);
|
||||||
productRefGroup = C41C1B3422B0097F00E7CF16 /* Products */;
|
productRefGroup = C41C1B3422B0097F00E7CF16 /* Products */;
|
||||||
projectDirPath = "";
|
projectDirPath = "";
|
||||||
projectRoot = "";
|
projectRoot = "";
|
||||||
@@ -399,11 +443,15 @@
|
|||||||
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 */,
|
5420395926135DC100FB00FA /* PrefsVC.swift in Sources */,
|
||||||
C41C1B4722B009A400E7CF16 /* Shell.swift in Sources */,
|
C41C1B4722B009A400E7CF16 /* Shell.swift in Sources */,
|
||||||
|
C4F2E43A2752F7D00020E974 /* PhpInstallation.swift in Sources */,
|
||||||
C41C1B4D22B0215A00E7CF16 /* Actions.swift in Sources */,
|
C41C1B4D22B0215A00E7CF16 /* Actions.swift in Sources */,
|
||||||
C48D0CA325CC992000CC7490 /* StatsView.swift in Sources */,
|
C48D0CA325CC992000CC7490 /* StatsView.swift in Sources */,
|
||||||
|
C4F2E4372752F0870020E974 /* HomebrewDiagnostics.swift in Sources */,
|
||||||
|
C41CD0292628D8EE0065BBED /* GlobalKeybindPreference.swift in Sources */,
|
||||||
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 */,
|
||||||
@@ -412,7 +460,8 @@
|
|||||||
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 */,
|
||||||
C41C1B4B22B019FF00E7CF16 /* PhpInstallation.swift in Sources */,
|
C42759672627662800093CAE /* NSMenuExtension.swift in Sources */,
|
||||||
|
C41C1B4B22B019FF00E7CF16 /* ActivePhpInstallation.swift in Sources */,
|
||||||
C49EAB46259FC305007F6C3B /* Paths.swift in Sources */,
|
C49EAB46259FC305007F6C3B /* Paths.swift in Sources */,
|
||||||
C476FF9822B0DD830098105B /* Alert.swift in Sources */,
|
C476FF9822B0DD830098105B /* Alert.swift in Sources */,
|
||||||
C474B00624C0E98C00066A22 /* LocalNotification.swift in Sources */,
|
C474B00624C0E98C00066A22 /* LocalNotification.swift in Sources */,
|
||||||
@@ -427,22 +476,27 @@
|
|||||||
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 /* ActivePhpInstallation.swift in Sources */,
|
||||||
C4F780B125D80B4D000DBC97 /* PhpExtension.swift in Sources */,
|
C4F780B125D80B4D000DBC97 /* PhpExtension.swift in Sources */,
|
||||||
C4F780CE25D80B75000DBC97 /* LocalNotification.swift in Sources */,
|
C4F780CE25D80B75000DBC97 /* LocalNotification.swift in Sources */,
|
||||||
C4FBFC532616485F00CDB8E1 /* PhpVersionDetectionTest.swift in Sources */,
|
C4FBFC532616485F00CDB8E1 /* PhpVersionDetectionTest.swift in Sources */,
|
||||||
C43A8A2425D9D20D00591B77 /* BrewJsonParserTest.swift in Sources */,
|
C43A8A2425D9D20D00591B77 /* BrewJsonParserTest.swift in Sources */,
|
||||||
C4F780CA25D80B75000DBC97 /* HomebrewPackage.swift in Sources */,
|
C4F780CA25D80B75000DBC97 /* HomebrewPackage.swift in Sources */,
|
||||||
C4F780C025D80B6E000DBC97 /* Startup.swift in Sources */,
|
C4F780C025D80B6E000DBC97 /* Startup.swift in Sources */,
|
||||||
|
C4F2E4382752F08D0020E974 /* HomebrewDiagnostics.swift in Sources */,
|
||||||
C4F780AE25D80B37000DBC97 /* ExtensionParserTest.swift in Sources */,
|
C4F780AE25D80B37000DBC97 /* ExtensionParserTest.swift in Sources */,
|
||||||
C4F780C725D80B75000DBC97 /* StatusMenu.swift in Sources */,
|
C4F780C725D80B75000DBC97 /* StatusMenu.swift in Sources */,
|
||||||
|
C42759682627662800093CAE /* NSMenuExtension.swift in Sources */,
|
||||||
C4F780CD25D80B75000DBC97 /* Alert.swift in Sources */,
|
C4F780CD25D80B75000DBC97 /* Alert.swift in Sources */,
|
||||||
C481F79726164A78004FBCFF /* PrefsVC.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 */,
|
||||||
|
C4F2E43B27530F750020E974 /* PhpInstallation.swift in Sources */,
|
||||||
C4F780BD25D80B65000DBC97 /* Constants.swift in Sources */,
|
C4F780BD25D80B65000DBC97 /* Constants.swift in Sources */,
|
||||||
C4F780C325D80B75000DBC97 /* HeaderView.swift in Sources */,
|
C4F780C325D80B75000DBC97 /* HeaderView.swift in Sources */,
|
||||||
C4F7809625D7FBF8000DBC97 /* Shell.swift in Sources */,
|
C4F7809625D7FBF8000DBC97 /* Shell.swift in Sources */,
|
||||||
@@ -605,7 +659,7 @@
|
|||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
CURRENT_PROJECT_VERSION = 54;
|
CURRENT_PROJECT_VERSION = 80;
|
||||||
DEVELOPMENT_TEAM = 8M54J5J787;
|
DEVELOPMENT_TEAM = 8M54J5J787;
|
||||||
ENABLE_HARDENED_RUNTIME = YES;
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
INFOPLIST_FILE = phpmon/Info.plist;
|
INFOPLIST_FILE = phpmon/Info.plist;
|
||||||
@@ -613,7 +667,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/../Frameworks",
|
"@executable_path/../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 3.3;
|
MARKETING_VERSION = 4.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon;
|
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
@@ -629,7 +683,7 @@
|
|||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
CURRENT_PROJECT_VERSION = 54;
|
CURRENT_PROJECT_VERSION = 80;
|
||||||
DEVELOPMENT_TEAM = 8M54J5J787;
|
DEVELOPMENT_TEAM = 8M54J5J787;
|
||||||
ENABLE_HARDENED_RUNTIME = YES;
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
INFOPLIST_FILE = phpmon/Info.plist;
|
INFOPLIST_FILE = phpmon/Info.plist;
|
||||||
@@ -637,7 +691,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/../Frameworks",
|
"@executable_path/../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 3.3;
|
MARKETING_VERSION = 4.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon;
|
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
@@ -658,7 +712,7 @@
|
|||||||
"@executable_path/../Frameworks",
|
"@executable_path/../Frameworks",
|
||||||
"@loader_path/../Frameworks",
|
"@loader_path/../Frameworks",
|
||||||
);
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = 11.1;
|
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "com.nicoverbruggen.phpmon-tests";
|
PRODUCT_BUNDLE_IDENTIFIER = "com.nicoverbruggen.phpmon-tests";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
@@ -679,7 +733,7 @@
|
|||||||
"@executable_path/../Frameworks",
|
"@executable_path/../Frameworks",
|
||||||
"@loader_path/../Frameworks",
|
"@loader_path/../Frameworks",
|
||||||
);
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = 11.1;
|
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "com.nicoverbruggen.phpmon-tests";
|
PRODUCT_BUNDLE_IDENTIFIER = "com.nicoverbruggen.phpmon-tests";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
@@ -718,6 +772,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 */;
|
||||||
}
|
}
|
||||||
|
93
README.md
93
README.md
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
**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.
|
**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/screenshot33.png" width="376px" 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>
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ PHP Monitor also gives you quick access to various useful functionality (like ac
|
|||||||
|
|
||||||
PHP Monitor is a universal application that runs on Apple Silicon **and** Intel-based Macs.
|
PHP Monitor is a universal application that runs on Apple Silicon **and** Intel-based Macs.
|
||||||
|
|
||||||
* macOS 10.14 Mojave or higher (works on macOS 11 Big Sur)
|
* macOS 10.14 Mojave or higher (works on macOS 11 Big Sur and macOS 12 Monterey)
|
||||||
* Homebrew is installed in `/usr/local/homebrew` or `/opt/homebrew`
|
* Homebrew is installed in `/usr/local/homebrew` or `/opt/homebrew`
|
||||||
* The brew formula `php` has to be installed (which version is detected)
|
* The brew formula `php` has to be installed (which version is detected)
|
||||||
* Laravel Valet 2.13 or higher
|
* Laravel Valet 2.13 or higher
|
||||||
@@ -30,7 +30,7 @@ _You may need to update your Valet installation to keep everything working if a
|
|||||||
|
|
||||||
## 🚀 How to install
|
## 🚀 How to install
|
||||||
|
|
||||||
You can install via Homebrew, or may download the latest [release](https://github.com/nicoverbruggen/phpmon/releases).
|
You can install via Homebrew (recommended), or may download the latest release on GitHub.
|
||||||
|
|
||||||
To install via Homebrew, run:
|
To install via Homebrew, run:
|
||||||
|
|
||||||
@@ -59,7 +59,26 @@ 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>
|
||||||
|
<li>PHP 8.2 (experimental)</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>
|
||||||
@@ -135,6 +154,35 @@ This problem is usually resolved by upgrading Valet and running `valet install`
|
|||||||
valet install
|
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>
|
||||||
@@ -150,7 +198,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 +210,8 @@ For Zend extensions:
|
|||||||
|
|
||||||
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,7 +235,14 @@ 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>
|
||||||
|
<summary><strong>The app has crashed!</strong></summary>
|
||||||
|
|
||||||
|
Please get in touch and open an issue. PHP Monitor shouldn't crash :)
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
@@ -201,11 +258,24 @@ You can find a [sponsor](https://nicoverbruggen.be/sponsor) link at the top of t
|
|||||||
|
|
||||||
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!).
|
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.
|
||||||
|
|
||||||
@@ -221,11 +291,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?
|
||||||
|
|
||||||
@@ -235,7 +302,7 @@ This app isn't very complicated after all. In the end, this just (conveniently)
|
|||||||
|
|
||||||
## 🔧 Build instructions
|
## 🔧 Build instructions
|
||||||
|
|
||||||
<img src="./docs/build.png" width="320px" alt="build button in Xcode"/>
|
<img src="./docs/build.png" width="404px" alt="build button in Xcode"/>
|
||||||
|
|
||||||
If you'd like to build PHP Monitor yourself, you need:
|
If you'd like to build PHP Monitor yourself, you need:
|
||||||
|
|
||||||
|
17
SECURITY.md
17
SECURITY.md
@@ -4,13 +4,16 @@
|
|||||||
|
|
||||||
Generally speaking, only the latest version of **PHP Monitor** is supported:
|
Generally speaking, only the latest version of **PHP Monitor** is supported:
|
||||||
|
|
||||||
| Version | Apple Silicon | Supported | Supported macOS | Deployment Target |
|
| Version | Apple silicon | Supported | Supported macOS | Deployment Target | Detected PHP Versions |
|
||||||
| ------- | ------------- | ------------------ | ----- | ----- |
|
| ------- | ------------- | ------------------ | ----- | ----- | ----- |
|
||||||
| 3.x | ✅ Universal binary | ✅ | Big Sur (11.0) | macOS 10.14+ |
|
| 4.0 | ✅ Universal binary | ✅ | Big Sur (11.0) and Monterey (12.0) | macOS 10.14+ | PHP 5.6—PHP 8.2 |
|
||||||
| 2.6 | ✅ Universal binary | ❌ | Big Sur (11.0) | macOS 10.14+ |
|
| 3.5 | ✅ Universal binary | ✅ | Big Sur (11.0) and Monterey (12.0) | macOS 10.14+ | PHP 5.6—PHP 8.2 |
|
||||||
| 2.5 | ✴️ Universal binary<br/>`/usr/local/homebrew` installations only | ❌ | Big Sur (11.0)<br/>Catalina (10.15) | macOS 10.14+ |
|
| 3.5 | ✅ Universal binary | ✅ | Big Sur (11.0) and Monterey (12.0) | macOS 10.14+ | PHP 5.6—PHP 8.2 |
|
||||||
| 2.4 | ✴️ Universal binary<br/>`/usr/local/homebrew` installations only | ❌ | Big Sur (11.0)<br/>Catalina (10.15) | macOS 10.14+ |
|
| 3.0—3.4 | ✅ Universal binary | ✅ | Big Sur (11.0) | macOS 10.14+ | PHP 5.6—PHP 8.1 |
|
||||||
| < 2.4 | ❌ Intel binary<br/>`/usr/local/homebrew` installations only | ❌ | Catalina (10.15) | macOS 10.14+ |
|
| 2.6 | ✅ Universal binary | ❌ | Big Sur (11.0) | macOS 10.14+ | PHP 5.6—PHP 8.0 |
|
||||||
|
| 2.5 | ✴️ Universal binary<br/>`/usr/local/homebrew` installations only | ❌ | Big Sur (11.0)<br/>Catalina (10.15) | macOS 10.14+ | not applicable |
|
||||||
|
| 2.4 | ✴️ Universal binary<br/>`/usr/local/homebrew` installations only | ❌ | Big Sur (11.0)<br/>Catalina (10.15) | macOS 10.14+ | not applicable |
|
||||||
|
| < 2.4 | ❌ Intel binary<br/>`/usr/local/homebrew` installations only | ❌ | Catalina (10.15) | macOS 10.14+ | not applicable |
|
||||||
|
|
||||||
## Reporting a vulnerability
|
## Reporting a vulnerability
|
||||||
|
|
||||||
|
BIN
docs/build.png
BIN
docs/build.png
Binary file not shown.
Before Width: | Height: | Size: 6.0 KiB After Width: | Height: | Size: 11 KiB |
Binary file not shown.
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 18 KiB |
Binary file not shown.
Before Width: | Height: | Size: 212 KiB |
BIN
docs/screenshot34.png
Normal file
BIN
docs/screenshot34.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 130 KiB |
@@ -23,6 +23,9 @@ class BrewJsonParserTest: XCTestCase {
|
|||||||
XCTAssertEqual(package.name, "php")
|
XCTAssertEqual(package.name, "php")
|
||||||
XCTAssertEqual(package.full_name, "php")
|
XCTAssertEqual(package.full_name, "php")
|
||||||
XCTAssertEqual(package.aliases.first!, "php@8.0")
|
XCTAssertEqual(package.aliases.first!, "php@8.0")
|
||||||
|
XCTAssertEqual(package.installed.contains(where: { installed in
|
||||||
|
installed.version.starts(with: "8.0")
|
||||||
|
}), true)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -23,24 +23,40 @@ 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// These 6 should be found
|
||||||
|
XCTAssertTrue(extensionNames.contains("xdebug"))
|
||||||
|
XCTAssertTrue(extensionNames.contains("imagick"))
|
||||||
|
XCTAssertTrue(extensionNames.contains("sodium-next"))
|
||||||
|
XCTAssertTrue(extensionNames.contains("opcache"))
|
||||||
|
XCTAssertTrue(extensionNames.contains("yaml"))
|
||||||
|
XCTAssertTrue(extensionNames.contains("custom"))
|
||||||
|
|
||||||
|
XCTAssertFalse(extensionNames.contains("fake"))
|
||||||
|
XCTAssertFalse(extensionNames.contains("nice"))
|
||||||
}
|
}
|
||||||
|
|
||||||
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, 6)
|
||||||
|
|
||||||
// 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)
|
||||||
|
@@ -1,5 +1,16 @@
|
|||||||
|
# These should be detected
|
||||||
|
|
||||||
zend_extension="xdebug.so"
|
zend_extension="xdebug.so"
|
||||||
; zend_extension="imagick.so"
|
; zend_extension="imagick.so"
|
||||||
|
zend_extension=/opt/homebrew/opt/php/lib/php/20200930/opcache.so
|
||||||
|
zend_extension="/opt/homebrew/opt/php/lib/php/20200930/yaml.so"
|
||||||
|
;zend_extension="sodium-next.so"
|
||||||
|
extension = custom.so
|
||||||
|
|
||||||
|
# These should not be detected
|
||||||
|
|
||||||
|
#zend_extension="/opt/homebrew/opt/php/lib/php/20200930/commented.so"
|
||||||
|
hextension = nice.so
|
||||||
|
|
||||||
[PHP]
|
[PHP]
|
||||||
|
|
||||||
|
@@ -18,25 +18,25 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
|
|||||||
(invoked by PHP Monitor) shell commands. It is used to
|
(invoked by PHP Monitor) shell commands. It is used to
|
||||||
invoke all commands in this application.
|
invoke all commands in this application.
|
||||||
*/
|
*/
|
||||||
let sharedShell : Shell
|
let sharedShell: Shell
|
||||||
|
|
||||||
/**
|
/**
|
||||||
The App singleton contains information about the state of
|
The App singleton contains information about the state of
|
||||||
the application and global variables.
|
the application and global variables.
|
||||||
*/
|
*/
|
||||||
let state : App
|
let state: App
|
||||||
|
|
||||||
/**
|
/**
|
||||||
The MainMenu singleton is responsible for rendering the
|
The MainMenu singleton is responsible for rendering the
|
||||||
menu bar item and its menu, as well as its actions.
|
menu bar item and its menu, as well as its actions.
|
||||||
*/
|
*/
|
||||||
let menu : MainMenu
|
let menu: MainMenu
|
||||||
|
|
||||||
/**
|
/**
|
||||||
The paths singleton that determines where Homebrew is installed,
|
The paths singleton that determines where Homebrew is installed,
|
||||||
and where to look for binaries.
|
and where to look for binaries.
|
||||||
*/
|
*/
|
||||||
let paths : Paths
|
let paths: Paths
|
||||||
|
|
||||||
// MARK: - Initializer
|
// MARK: - Initializer
|
||||||
|
|
||||||
@@ -77,4 +77,5 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
|
|||||||
) -> Bool {
|
) -> Bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -9,11 +9,21 @@ import Cocoa
|
|||||||
|
|
||||||
class Constants {
|
class Constants {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The latest PHP version that is considered to be stable at the time of release.
|
||||||
|
* This version number is currently not used (only as a default fallback).
|
||||||
|
*/
|
||||||
|
static let LatestStablePhpVersion = "8.1"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The PHP versions supported by this application.
|
* The PHP versions supported by this application.
|
||||||
* Versions that do not appear in this array are omitted from the list.
|
* Versions that do not appear in this array are omitted from the list.
|
||||||
*/
|
*/
|
||||||
static let SupportedPhpVersions = [
|
static let SupportedPhpVersions = [
|
||||||
|
// ====================
|
||||||
|
// STABLE RELEASES
|
||||||
|
// ====================
|
||||||
|
// Versions of PHP that are stable and are supported.
|
||||||
"5.6",
|
"5.6",
|
||||||
"7.0",
|
"7.0",
|
||||||
"7.1",
|
"7.1",
|
||||||
@@ -21,7 +31,16 @@ class Constants {
|
|||||||
"7.3",
|
"7.3",
|
||||||
"7.4",
|
"7.4",
|
||||||
"8.0",
|
"8.0",
|
||||||
"8.1"
|
"8.1",
|
||||||
|
|
||||||
|
// ====================
|
||||||
|
// EXPERIMENTAL SUPPORT
|
||||||
|
// ====================
|
||||||
|
// Every release that supports the next release will always support the next
|
||||||
|
// dev release. In this case, that means that the version below is detected.
|
||||||
|
"8.2"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -29,9 +29,26 @@ class Actions {
|
|||||||
|
|
||||||
print("The PHP versions that were detected are: \(versionsOnly)")
|
print("The PHP versions that were detected are: \(versionsOnly)")
|
||||||
|
|
||||||
|
App.shared.availablePhpVersions = versionsOnly
|
||||||
|
Actions.extractPhpLongVersions()
|
||||||
|
|
||||||
return versionsOnly
|
return versionsOnly
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
This method extracts the PHP full version number after finding the php installation folders.
|
||||||
|
To be refactored at some later point, I'd like to cache the `PhpInstallation` objects instead of just the version number at some point.
|
||||||
|
*/
|
||||||
|
public static func extractPhpLongVersions()
|
||||||
|
{
|
||||||
|
var mappedVersions: [String: PhpInstallation] = [:]
|
||||||
|
App.shared.availablePhpVersions.forEach { version in
|
||||||
|
mappedVersions[version] = PhpInstallation(version)
|
||||||
|
}
|
||||||
|
|
||||||
|
App.shared.cachedPhpInstallations = mappedVersions
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Extracts valid PHP versions from an array of strings.
|
Extracts valid PHP versions from an array of strings.
|
||||||
This array of strings is usually retrieved from `grep`.
|
This array of strings is usually retrieved from `grep`.
|
||||||
@@ -78,6 +95,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
|
||||||
@@ -87,18 +111,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)) {
|
||||||
|
print("All versions have been unlinked!")
|
||||||
brew("link \(formula) --overwrite --force")
|
print("Linking the new version!")
|
||||||
brew("services start \(formula)", sudo: true)
|
|
||||||
|
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
|
||||||
@@ -109,6 +155,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")];
|
||||||
@@ -117,8 +170,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
|
||||||
@@ -162,7 +216,16 @@ 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_replacement = replacement.replacingOccurrences(of: "/", with: "\\/")
|
||||||
|
|
||||||
|
// Check if gsed exists; it is able to follow symlinks, which we want to do to toggle the extension
|
||||||
|
if Shell.fileExists("\(Paths.binPath)/gsed") {
|
||||||
|
Shell.run("\(Paths.binPath)/gsed -i --follow-symlinks 's/\(e_original)/\(e_replacement)/g' \(file)")
|
||||||
|
} else {
|
||||||
|
Shell.run("sed -i '' 's/\(e_original)/\(e_replacement)/g' \(file)")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -176,4 +239,5 @@ class Actions {
|
|||||||
.trimmingCharacters(in: .whitespacesAndNewlines)
|
.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
.contains("YES")
|
.contains("YES")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@@ -4,16 +4,24 @@
|
|||||||
//
|
//
|
||||||
// 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()
|
||||||
|
|
||||||
static var phpInstall: PhpInstallation? {
|
init() {
|
||||||
|
loadGlobalHotkey()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Information about the currently linked PHP installation. */
|
||||||
|
static var phpInstall: ActivePhpInstallation? {
|
||||||
return App.shared.currentInstall
|
return App.shared.currentInstall
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Whether the app is busy doing something. Used to determine what UI to display. */
|
||||||
static var busy: Bool {
|
static var busy: Bool {
|
||||||
return App.shared.busy
|
return App.shared.busy
|
||||||
}
|
}
|
||||||
@@ -34,13 +42,18 @@ class App {
|
|||||||
/**
|
/**
|
||||||
The currently active installation of PHP.
|
The currently active installation of PHP.
|
||||||
*/
|
*/
|
||||||
var currentInstall: PhpInstallation? = nil
|
var currentInstall: ActivePhpInstallation? = nil
|
||||||
|
|
||||||
/**
|
/**
|
||||||
All available versions of PHP.
|
All available versions of PHP.
|
||||||
*/
|
*/
|
||||||
var availablePhpVersions : [String] = []
|
var availablePhpVersions : [String] = []
|
||||||
|
|
||||||
|
/**
|
||||||
|
Cached information about the PHP installations; contains only the full version number at this point.
|
||||||
|
*/
|
||||||
|
var cachedPhpInstallations : [String: PhpInstallation] = [:]
|
||||||
|
|
||||||
/**
|
/**
|
||||||
The timer that will periodically fetch the PHP version that is currently active.
|
The timer that will periodically fetch the PHP version that is currently active.
|
||||||
*/
|
*/
|
||||||
@@ -49,7 +62,7 @@ class App {
|
|||||||
/**
|
/**
|
||||||
Information we were able to discern from the Homebrew info command (as JSON).
|
Information we were able to discern from the Homebrew info command (as JSON).
|
||||||
*/
|
*/
|
||||||
var brewPhpPackage: HomebrewPackage? = nil {
|
var brewPhpPackage: HomebrewPackage! = nil {
|
||||||
didSet {
|
didSet {
|
||||||
brewPhpVersion = brewPhpPackage!.version
|
brewPhpVersion = brewPhpPackage!.version
|
||||||
}
|
}
|
||||||
@@ -61,9 +74,57 @@ class App {
|
|||||||
If you're up to date, `php` will be aliased to the latest version,
|
If you're up to date, `php` will be aliased to the latest version,
|
||||||
but that might not be the case.
|
but that might not be the case.
|
||||||
|
|
||||||
We'll technically default to version 8.0, but the information should always be loaded
|
We'll technically default to the version in Constants.swift, but the information
|
||||||
from Homebrew itself upon starting the application.
|
should always be loaded from Homebrew itself upon startup.
|
||||||
*/
|
*/
|
||||||
var brewPhpVersion: String = "8.0"
|
var brewPhpVersion: String = Constants.LatestStablePhpVersion
|
||||||
|
|
||||||
|
/**
|
||||||
|
The shortcut the user has requested.
|
||||||
|
*/
|
||||||
|
var shortcutHotkey: HotKey? = nil {
|
||||||
|
didSet {
|
||||||
|
self.setupGlobalHotkeyListener()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Methods
|
||||||
|
|
||||||
|
/**
|
||||||
|
On startup, the preferences should be loaded from the .plist, and we'll enable the shortcut if it is set.
|
||||||
|
*/
|
||||||
|
private func loadGlobalHotkey() {
|
||||||
|
// Make sure we can retrieve the hotkey from preferences; if we cannot, no hotkey is set
|
||||||
|
guard let hotkey = Preferences.preferences[.globalHotkey] as? String else {
|
||||||
|
print("No global hotkey loaded")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure we can parse the JSON into the desired format; if we cannot, no hotkey is set
|
||||||
|
guard let keybindPref = GlobalKeybindPreference.fromJson(hotkey) else {
|
||||||
|
print("No global hotkey loaded, could not be parsed!")
|
||||||
|
self.shortcutHotkey = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.shortcutHotkey = HotKey(keyCombo: KeyCombo(
|
||||||
|
carbonKeyCode: keybindPref.keyCode,
|
||||||
|
carbonModifiers: keybindPref.carbonFlags
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Sets up the action that needs to occur when the shortcut key is pressed (open the menu).
|
||||||
|
*/
|
||||||
|
private func setupGlobalHotkeyListener() {
|
||||||
|
guard let hotkey = self.shortcutHotkey else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
hotkey.keyDownHandler = {
|
||||||
|
MainMenu.shared.statusItem.button?.performClick(nil)
|
||||||
|
NSApplication.shared.activate(ignoringOtherApps: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="17701" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="19455" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<deployment identifier="macosx"/>
|
<deployment identifier="macosx"/>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="17701"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="19455"/>
|
||||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<scenes>
|
<scenes>
|
||||||
@@ -58,7 +58,7 @@
|
|||||||
<!--Window Controller-->
|
<!--Window Controller-->
|
||||||
<scene sceneID="PQa-AT-b2a">
|
<scene sceneID="PQa-AT-b2a">
|
||||||
<objects>
|
<objects>
|
||||||
<windowController storyboardIdentifier="preferencesWindow" showSeguePresentationStyle="single" id="hLJ-Fd-wRr" sceneMemberID="viewController">
|
<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">
|
<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"/>
|
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
|
||||||
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
||||||
@@ -84,22 +84,25 @@
|
|||||||
<scene sceneID="iyi-IS-7Ps">
|
<scene sceneID="iyi-IS-7Ps">
|
||||||
<objects>
|
<objects>
|
||||||
<viewController title="Preferences" storyboardIdentifier="preferences" showSeguePresentationStyle="single" id="AW2-rV-rbS" customClass="PrefsVC" customModule="PHP_Monitor" customModuleProvider="target" sceneMemberID="viewController">
|
<viewController title="Preferences" storyboardIdentifier="preferences" showSeguePresentationStyle="single" id="AW2-rV-rbS" customClass="PrefsVC" customModule="PHP_Monitor" customModuleProvider="target" sceneMemberID="viewController">
|
||||||
<view key="view" id="Pf1-A5-3Xz">
|
<view key="view" wantsLayer="YES" misplaced="YES" id="Pf1-A5-3Xz">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="462" height="139"/>
|
<rect key="frame" x="0.0" y="0.0" width="574" height="311"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="GSr-K5-3yw">
|
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="GSr-K5-3yw">
|
||||||
<rect key="frame" x="373" y="13" width="76" height="32"/>
|
<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">
|
<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"/>
|
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||||
<font key="font" metaFont="system"/>
|
<font key="font" metaFont="system"/>
|
||||||
|
<string key="keyEquivalent" base64-UTF8="YES">
|
||||||
|
Gw
|
||||||
|
</string>
|
||||||
</buttonCell>
|
</buttonCell>
|
||||||
<connections>
|
<connections>
|
||||||
<action selector="pressed:" target="AW2-rV-rbS" id="8dA-y4-voq"/>
|
<action selector="pressed:" target="AW2-rV-rbS" id="8dA-y4-voq"/>
|
||||||
</connections>
|
</connections>
|
||||||
</button>
|
</button>
|
||||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="MEf-MN-oXt">
|
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="MEf-MN-oXt">
|
||||||
<rect key="frame" x="18" y="102" width="424" height="18"/>
|
<rect key="frame" x="148" y="274" width="406" height="18"/>
|
||||||
<buttonCell key="cell" type="check" title="DYN_ICON" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="m5s-qp-Iaj">
|
<buttonCell key="cell" type="check" title="DYN_ICON" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="m5s-qp-Iaj">
|
||||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||||
<font key="font" metaFont="system"/>
|
<font key="font" metaFont="system"/>
|
||||||
@@ -109,35 +112,177 @@
|
|||||||
</connections>
|
</connections>
|
||||||
</button>
|
</button>
|
||||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="JrH-aa-AzL">
|
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="JrH-aa-AzL">
|
||||||
<rect key="frame" x="18" y="81" width="426" height="14"/>
|
<rect key="frame" x="148" y="253" width="408" height="14"/>
|
||||||
<textFieldCell key="cell" title="DYN_ICON_DESC" id="MHA-Xt-xgF">
|
<textFieldCell key="cell" title="DYN_ICON_DESC" id="MHA-Xt-xgF">
|
||||||
<font key="font" metaFont="system" size="11"/>
|
<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="smallSystem"/>
|
||||||
|
</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="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||||
</textFieldCell>
|
</textFieldCell>
|
||||||
</textField>
|
</textField>
|
||||||
|
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="31d-gd-auR">
|
||||||
|
<rect key="frame" x="18" y="275" 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>
|
||||||
|
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="vSc-oQ-NC5">
|
||||||
|
<rect key="frame" x="148" y="220" width="121" height="18"/>
|
||||||
|
<buttonCell key="cell" type="check" title="FULL_PHP_VER" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="eCd-ja-EwE">
|
||||||
|
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||||
|
<font key="font" metaFont="system"/>
|
||||||
|
</buttonCell>
|
||||||
|
<connections>
|
||||||
|
<action selector="toggledFullPhpVersion:" target="AW2-rV-rbS" id="RCY-Ah-sLM"/>
|
||||||
|
</connections>
|
||||||
|
</button>
|
||||||
|
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="t24-LR-wKz">
|
||||||
|
<rect key="frame" x="148" y="199" width="123" height="14"/>
|
||||||
|
<textFieldCell key="cell" title="FULL_PHP_VER_DESC" id="8gG-qs-mHR">
|
||||||
|
<font key="font" metaFont="smallSystem"/>
|
||||||
|
<color key="textColor" name="secondaryLabelColor" catalog="System" colorSpace="catalog"/>
|
||||||
|
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||||
|
</textFieldCell>
|
||||||
|
</textField>
|
||||||
|
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ogC-wz-ZfO">
|
||||||
|
<rect key="frame" x="18" y="153" width="124" height="16"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstAttribute="width" constant="120" id="i9O-6m-Gr9"/>
|
||||||
|
</constraints>
|
||||||
|
<textFieldCell key="cell" lineBreakMode="clipping" alignment="right" title="PREF_SERVICES:" id="bm4-rf-kCF">
|
||||||
|
<font key="font" metaFont="system"/>
|
||||||
|
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||||
|
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||||
|
</textFieldCell>
|
||||||
|
</textField>
|
||||||
|
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="47u-9B-eDu">
|
||||||
|
<rect key="frame" x="148" y="152" width="126" height="18"/>
|
||||||
|
<buttonCell key="cell" type="check" title="AUTO_RESTART" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="n1d-l4-inL">
|
||||||
|
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||||
|
<font key="font" metaFont="system"/>
|
||||||
|
</buttonCell>
|
||||||
|
<connections>
|
||||||
|
<action selector="toggledAutoRestartServices:" target="AW2-rV-rbS" id="THn-nu-IiJ"/>
|
||||||
|
</connections>
|
||||||
|
</button>
|
||||||
|
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ObP-GE-ejZ">
|
||||||
|
<rect key="frame" x="148" y="131" width="126" height="14"/>
|
||||||
|
<textFieldCell key="cell" title="AUTO_RESTART_DESC" id="F9P-iQ-gBk">
|
||||||
|
<font key="font" metaFont="smallSystem"/>
|
||||||
|
<color key="textColor" name="secondaryLabelColor" catalog="System" colorSpace="catalog"/>
|
||||||
|
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||||
|
</textFieldCell>
|
||||||
|
</textField>
|
||||||
</subviews>
|
</subviews>
|
||||||
<constraints>
|
<constraints>
|
||||||
|
<constraint firstItem="ogC-wz-ZfO" firstAttribute="trailing" secondItem="31d-gd-auR" secondAttribute="trailing" id="2Lr-Ht-qKI"/>
|
||||||
|
<constraint firstItem="t24-LR-wKz" firstAttribute="leading" secondItem="vSc-oQ-NC5" secondAttribute="leading" id="3tK-kp-q5R"/>
|
||||||
|
<constraint firstItem="t24-LR-wKz" firstAttribute="top" secondItem="vSc-oQ-NC5" secondAttribute="bottom" constant="8" symbolic="YES" id="4Ft-lN-vwA"/>
|
||||||
<constraint firstAttribute="trailing" secondItem="JrH-aa-AzL" secondAttribute="trailing" constant="20" symbolic="YES" id="8iM-Xf-ShU"/>
|
<constraint firstAttribute="trailing" secondItem="JrH-aa-AzL" secondAttribute="trailing" constant="20" symbolic="YES" id="8iM-Xf-ShU"/>
|
||||||
|
<constraint firstItem="ObP-GE-ejZ" firstAttribute="leading" secondItem="47u-9B-eDu" secondAttribute="leading" id="ASF-WR-A3X"/>
|
||||||
<constraint firstAttribute="trailing" secondItem="GSr-K5-3yw" secondAttribute="trailing" constant="20" symbolic="YES" id="AT9-5F-6g1"/>
|
<constraint firstAttribute="trailing" secondItem="GSr-K5-3yw" secondAttribute="trailing" constant="20" symbolic="YES" id="AT9-5F-6g1"/>
|
||||||
<constraint firstItem="MEf-MN-oXt" firstAttribute="top" secondItem="Pf1-A5-3Xz" secondAttribute="top" constant="20" symbolic="YES" id="FJC-Lx-L8a"/>
|
<constraint firstItem="YsQ-AZ-Aei" firstAttribute="leading" secondItem="V7b-jv-oCB" secondAttribute="trailing" constant="12" symbolic="YES" id="Bk6-4V-GLk"/>
|
||||||
<constraint firstItem="MEf-MN-oXt" firstAttribute="leading" secondItem="Pf1-A5-3Xz" secondAttribute="leading" constant="20" symbolic="YES" id="Imd-YJ-Ae7"/>
|
<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="vSc-oQ-NC5" firstAttribute="leading" secondItem="JrH-aa-AzL" secondAttribute="leading" id="FVa-vu-VGJ"/>
|
||||||
|
<constraint firstItem="MEf-MN-oXt" firstAttribute="leading" secondItem="31d-gd-auR" secondAttribute="trailing" constant="10" id="G5S-JV-re3"/>
|
||||||
|
<constraint firstItem="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="ObP-GE-ejZ" firstAttribute="top" secondItem="47u-9B-eDu" secondAttribute="bottom" constant="8" symbolic="YES" id="JqR-Jd-SoR"/>
|
||||||
|
<constraint firstItem="JrH-aa-AzL" firstAttribute="leading" secondItem="MEf-MN-oXt" secondAttribute="leading" id="K2H-Af-2qK"/>
|
||||||
|
<constraint firstItem="5ZK-BG-o1t" firstAttribute="top" secondItem="ObP-GE-ejZ" secondAttribute="bottom" constant="30" id="LO4-8j-ihp"/>
|
||||||
|
<constraint firstItem="47u-9B-eDu" firstAttribute="top" secondItem="ogC-wz-ZfO" secondAttribute="top" id="T9j-v2-fSW"/>
|
||||||
<constraint firstItem="JrH-aa-AzL" firstAttribute="top" secondItem="MEf-MN-oXt" secondAttribute="bottom" constant="8" symbolic="YES" id="Vf8-fx-H50"/>
|
<constraint firstItem="JrH-aa-AzL" firstAttribute="top" secondItem="MEf-MN-oXt" secondAttribute="bottom" constant="8" symbolic="YES" id="Vf8-fx-H50"/>
|
||||||
|
<constraint firstItem="MEf-MN-oXt" firstAttribute="firstBaseline" secondItem="31d-gd-auR" secondAttribute="firstBaseline" id="W36-bE-iAT"/>
|
||||||
|
<constraint firstItem="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 firstAttribute="bottom" secondItem="GSr-K5-3yw" secondAttribute="bottom" constant="20" symbolic="YES" id="dAS-yW-vua"/>
|
||||||
<constraint firstItem="JrH-aa-AzL" firstAttribute="leading" secondItem="MEf-MN-oXt" secondAttribute="leading" id="dzR-S7-M6U"/>
|
<constraint firstItem="vSc-oQ-NC5" firstAttribute="top" secondItem="JrH-aa-AzL" secondAttribute="bottom" constant="16" id="hQf-4s-iHn"/>
|
||||||
<constraint firstItem="GSr-K5-3yw" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="Pf1-A5-3Xz" secondAttribute="leading" constant="20" symbolic="YES" id="mTE-WD-54L"/>
|
<constraint firstItem="GSr-K5-3yw" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="Pf1-A5-3Xz" secondAttribute="leading" constant="20" symbolic="YES" id="mTE-WD-54L"/>
|
||||||
|
<constraint firstItem="47u-9B-eDu" firstAttribute="leading" secondItem="MEf-MN-oXt" secondAttribute="leading" id="n8B-C8-dXs"/>
|
||||||
|
<constraint firstItem="31d-gd-auR" firstAttribute="leading" secondItem="Pf1-A5-3Xz" secondAttribute="leading" constant="20" symbolic="YES" id="o0J-yT-TDX"/>
|
||||||
|
<constraint firstItem="ogC-wz-ZfO" firstAttribute="top" secondItem="t24-LR-wKz" secondAttribute="bottom" constant="30" id="oXh-LE-sRS"/>
|
||||||
<constraint firstAttribute="trailing" secondItem="MEf-MN-oXt" secondAttribute="trailing" constant="20" symbolic="YES" id="pJg-zj-cBs"/>
|
<constraint firstAttribute="trailing" secondItem="MEf-MN-oXt" secondAttribute="trailing" constant="20" symbolic="YES" id="pJg-zj-cBs"/>
|
||||||
|
<constraint firstItem="GSr-K5-3yw" firstAttribute="top" secondItem="1TO-9H-z2d" secondAttribute="bottom" constant="20" id="pMZ-Gx-Jmm"/>
|
||||||
</constraints>
|
</constraints>
|
||||||
</view>
|
</view>
|
||||||
<connections>
|
<connections>
|
||||||
|
<outlet property="buttonAutoRestartServices" destination="47u-9B-eDu" id="kyg-BX-PQK"/>
|
||||||
|
<outlet property="buttonClearShortcut" destination="YsQ-AZ-Aei" id="1xo-hk-HgM"/>
|
||||||
<outlet property="buttonClose" destination="GSr-K5-3yw" id="d4I-Cf-gXD"/>
|
<outlet property="buttonClose" destination="GSr-K5-3yw" id="d4I-Cf-gXD"/>
|
||||||
|
<outlet property="buttonDisplayFullPhpVersion" destination="vSc-oQ-NC5" id="ZLa-Vf-4Dq"/>
|
||||||
<outlet property="buttonDynamicIcon" destination="MEf-MN-oXt" id="qEN-Vg-EZS"/>
|
<outlet property="buttonDynamicIcon" destination="MEf-MN-oXt" id="qEN-Vg-EZS"/>
|
||||||
|
<outlet property="buttonSetShortcut" destination="V7b-jv-oCB" id="2aS-S4-cKR"/>
|
||||||
|
<outlet property="labelAutoRestartServices" destination="ObP-GE-ejZ" id="uwY-D7-Uve"/>
|
||||||
|
<outlet property="labelDisplayFullPhpVersion" destination="t24-LR-wKz" id="wYj-Z0-a3h"/>
|
||||||
<outlet property="labelDynamicIcon" destination="JrH-aa-AzL" id="CFc-fF-oPq"/>
|
<outlet property="labelDynamicIcon" destination="JrH-aa-AzL" id="CFc-fF-oPq"/>
|
||||||
|
<outlet property="labelShortcut" destination="1TO-9H-z2d" id="paF-hK-78x"/>
|
||||||
|
<outlet property="leftLabelDynamicIcon" destination="31d-gd-auR" id="ANZ-Zs-4d7"/>
|
||||||
|
<outlet property="leftLabelGlobalShortcut" destination="5ZK-BG-o1t" id="73E-9i-cg8"/>
|
||||||
|
<outlet property="leftLabelServices" destination="ogC-wz-ZfO" id="BYx-Gv-N1p"/>
|
||||||
</connections>
|
</connections>
|
||||||
</viewController>
|
</viewController>
|
||||||
<customObject id="eQC-8B-FkX" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
<customObject id="eQC-8B-FkX" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||||
</objects>
|
</objects>
|
||||||
<point key="canvasLocation" x="216" y="319"/>
|
<point key="canvasLocation" x="264" y="457"/>
|
||||||
</scene>
|
</scene>
|
||||||
</scenes>
|
</scenes>
|
||||||
</document>
|
</document>
|
||||||
|
@@ -1,115 +0,0 @@
|
|||||||
//
|
|
||||||
// PhpInstallation.swift
|
|
||||||
// PHP Monitor
|
|
||||||
//
|
|
||||||
// Copyright © 2021 Nico Verbruggen. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
class PhpInstallation {
|
|
||||||
|
|
||||||
var version: Version
|
|
||||||
var configuration: Configuration
|
|
||||||
var extensions: [PhpExtension]
|
|
||||||
|
|
||||||
// MARK: - Computed
|
|
||||||
|
|
||||||
var formula: String {
|
|
||||||
return (version.short == App.shared.brewPhpVersion) ? "php" : "php@\(version.short)"
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Initializer
|
|
||||||
|
|
||||||
init() {
|
|
||||||
// Show information about the current version
|
|
||||||
version = Self.getVersion()
|
|
||||||
|
|
||||||
// If an error occurred, exit early
|
|
||||||
if (version.error) {
|
|
||||||
configuration = Configuration()
|
|
||||||
extensions = []
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load extension information
|
|
||||||
let path = URL(fileURLWithPath: "\(Paths.etcPath)/php/\(version.short)/php.ini")
|
|
||||||
|
|
||||||
extensions = PhpExtension.load(from: path)
|
|
||||||
|
|
||||||
// Get configuration values
|
|
||||||
configuration = Configuration(
|
|
||||||
memory_limit: Self.getByteCount(key: "memory_limit"),
|
|
||||||
upload_max_filesize: Self.getByteCount(key: "upload_max_filesize"),
|
|
||||||
post_max_size: Self.getByteCount(key: "post_max_size")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
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.
|
|
||||||
*/
|
|
||||||
private static func getVersion() -> Version {
|
|
||||||
var versionStruct = Version()
|
|
||||||
let version = Command.execute(path: Paths.php, arguments: ["-r", "print phpversion();"])
|
|
||||||
|
|
||||||
if (version == "" || version.contains("Warning") || version.contains("Error")) {
|
|
||||||
versionStruct.short = "💩 BROKEN"
|
|
||||||
versionStruct.long = "";
|
|
||||||
versionStruct.error = true
|
|
||||||
return versionStruct;
|
|
||||||
}
|
|
||||||
|
|
||||||
// That's the long version
|
|
||||||
versionStruct.long = version
|
|
||||||
|
|
||||||
// Next up, let's strip away the minor version number
|
|
||||||
let segments = versionStruct.long.components(separatedBy: ".")
|
|
||||||
|
|
||||||
// Get the first two elements
|
|
||||||
versionStruct.short = segments[0...1].joined(separator: ".")
|
|
||||||
|
|
||||||
return versionStruct
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Retrieves the display value for a specific key in the `.ini` file.
|
|
||||||
|
|
||||||
The following values are valid:
|
|
||||||
* -1: unlimited (show the infinity icon)
|
|
||||||
* 10000: an integer = amount of bytes
|
|
||||||
* 1K, 1M, 1G = shorthand for kilobytes, megabytes and gigabytes
|
|
||||||
|
|
||||||
If none of these notations are used, the _fallback_ value is used. We'll show an emoji to indicate something has gone wrong here.
|
|
||||||
To clarify, B gets appended to valid values. As a result, "5M" (valid) becomes "5MB", and "5MB" (invalid) becomes ⚠️.
|
|
||||||
|
|
||||||
- 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 {
|
|
||||||
let value = Command.execute(path: Paths.php, arguments: ["-r", "echo ini_get('\(key)');"])
|
|
||||||
|
|
||||||
// Check if the value is unlimited
|
|
||||||
if (value == "-1") {
|
|
||||||
return "∞"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the syntax is valid otherwise
|
|
||||||
let regex = try! NSRegularExpression(pattern: #"^([0-9]*)(K|M|G|)$"#, options: [])
|
|
||||||
let match = regex.matches(in: value, options: [], range: NSMakeRange(0, value.count)).first
|
|
||||||
return (match == nil) ? "⚠️" : "\(value)B"
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Structs
|
|
||||||
|
|
||||||
struct Version {
|
|
||||||
var short = "???"
|
|
||||||
var long = "???"
|
|
||||||
var error = false
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Configuration {
|
|
||||||
var memory_limit = "???"
|
|
||||||
var upload_max_filesize = "???"
|
|
||||||
var post_max_size = "???"
|
|
||||||
}
|
|
||||||
}
|
|
@@ -119,9 +119,14 @@ class Startup {
|
|||||||
|
|
||||||
DispatchQueue.main.async { [self] in
|
DispatchQueue.main.async { [self] in
|
||||||
// Present the information to the user
|
// Present the information to the user
|
||||||
Alert.notify(message: messageText, info: informativeText)
|
Alert.notify(
|
||||||
|
message: messageText,
|
||||||
|
info: informativeText,
|
||||||
|
style: breaking ? .critical : .warning
|
||||||
|
)
|
||||||
// Only breaking issues will throw the extra retry modal
|
// Only breaking issues will throw the extra retry modal
|
||||||
breaking ? failureCallback() : ()
|
breaking ? failureCallback() : ()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@@ -7,11 +7,12 @@
|
|||||||
|
|
||||||
import Cocoa
|
import Cocoa
|
||||||
|
|
||||||
extension Date
|
extension Date {
|
||||||
{
|
|
||||||
func toString() -> String {
|
func toString() -> String {
|
||||||
let dateFormatter = DateFormatter()
|
let dateFormatter = DateFormatter()
|
||||||
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
|
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
|
||||||
return dateFormatter.string(from: self)
|
return dateFormatter.string(from: self)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
18
phpmon/Domain/Extensions/NSMenuExtension.swift
Normal file
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -33,4 +33,5 @@ extension String {
|
|||||||
let end = r.upperBound
|
let end = r.upperBound
|
||||||
return String(self[start ..< end])
|
return String(self[start ..< end])
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -7,17 +7,19 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
import Cocoa
|
import Cocoa
|
||||||
|
|
||||||
// Adapted from: https://stackoverflow.com/a/46268778
|
// Adapted from: https://stackoverflow.com/a/46268778
|
||||||
|
|
||||||
protocol XibLoadable {
|
protocol XibLoadable {
|
||||||
|
|
||||||
static var xibName: String? { get }
|
static var xibName: String? { get }
|
||||||
static func createFromXib(in bundle: Bundle) -> Self?
|
static func createFromXib(in bundle: Bundle) -> Self?
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension XibLoadable where Self: NSView {
|
extension XibLoadable where Self: NSView {
|
||||||
|
|
||||||
static var xibName: String? {
|
static var xibName: String? {
|
||||||
return String(describing: Self.self)
|
return String(describing: Self.self)
|
||||||
}
|
}
|
||||||
@@ -30,4 +32,5 @@ extension XibLoadable where Self: NSView {
|
|||||||
let views = Array<Any>(results).filter { $0 is Self }
|
let views = Array<Any>(results).filter { $0 is Self }
|
||||||
return views.last as? Self
|
return views.last as? Self
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -13,9 +13,11 @@ class Alert {
|
|||||||
messageText: String,
|
messageText: String,
|
||||||
informativeText: String,
|
informativeText: String,
|
||||||
buttonTitle: String = "OK",
|
buttonTitle: String = "OK",
|
||||||
secondButtonTitle: String = ""
|
secondButtonTitle: String = "",
|
||||||
|
style: NSAlert.Style = .informational
|
||||||
) -> Bool {
|
) -> Bool {
|
||||||
let alert = NSAlert.init()
|
let alert = NSAlert.init()
|
||||||
|
alert.alertStyle = style
|
||||||
alert.messageText = messageText
|
alert.messageText = messageText
|
||||||
alert.informativeText = informativeText
|
alert.informativeText = informativeText
|
||||||
alert.addButton(withTitle: buttonTitle)
|
alert.addButton(withTitle: buttonTitle)
|
||||||
@@ -25,8 +27,8 @@ class Alert {
|
|||||||
return alert.runModal() == .alertFirstButtonReturn
|
return alert.runModal() == .alertFirstButtonReturn
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func notify(message: String, info: String) {
|
public static func notify(message: String, info: String, style: NSAlert.Style = .informational) {
|
||||||
_ = self.present(messageText: message, informativeText: info, buttonTitle: "OK", secondButtonTitle: "")
|
_ = self.present(messageText: message, informativeText: info, buttonTitle: "OK", secondButtonTitle: "", style: style)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
70
phpmon/Domain/Helpers/HomebrewDiagnostics.swift
Normal file
70
phpmon/Domain/Helpers/HomebrewDiagnostics.swift
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
//
|
||||||
|
// AliasConflict.swift
|
||||||
|
// PHP Monitor
|
||||||
|
//
|
||||||
|
// Created by Nico Verbruggen on 28/11/2021.
|
||||||
|
// Copyright © 2021 Nico Verbruggen. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
class HomebrewDiagnostics {
|
||||||
|
|
||||||
|
enum Errors: String {
|
||||||
|
case aliasConflict = "alias_conflict"
|
||||||
|
}
|
||||||
|
|
||||||
|
static let shared = HomebrewDiagnostics()
|
||||||
|
var errors: [HomebrewDiagnostics.Errors] = []
|
||||||
|
|
||||||
|
init() {
|
||||||
|
if self.determineAliasConflicts() {
|
||||||
|
self.errors.append(.aliasConflict)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
It is possible to have the `shivammathur/php` tap installed, and for the core homebrew information to be outdated.
|
||||||
|
This will then result in two different aliases claiming to point to the same formula (`php`).
|
||||||
|
This will break all linking functionality in PHP Monitor, and the user needs to be informed of this.
|
||||||
|
|
||||||
|
This check only needs to be performed if the `shivammathur/php` tap is active.
|
||||||
|
*/
|
||||||
|
public func determineAliasConflicts() -> Bool
|
||||||
|
{
|
||||||
|
let tapAlias = Shell.pipe("\(Paths.brew) info shivammathur/php/php --json")
|
||||||
|
|
||||||
|
if tapAlias.contains("brew tap shivammathur/php") || tapAlias.contains("Error") {
|
||||||
|
print("The user does not appear to have tapped: shivammathur/php")
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
print("The user DOES have the following tapped: shivammathur/php")
|
||||||
|
print("Checking for `php` formula conflicts...")
|
||||||
|
|
||||||
|
let tapPhp = try! JSONDecoder().decode(
|
||||||
|
[HomebrewPackage].self,
|
||||||
|
from: tapAlias.data(using: .utf8)!
|
||||||
|
).first!
|
||||||
|
|
||||||
|
if tapPhp.version != App.shared.brewPhpVersion {
|
||||||
|
print("The `php` formula alias seems to be the different between the tap and core. This could be a problem!")
|
||||||
|
print("Determining whether both of these versions are installed...")
|
||||||
|
|
||||||
|
let bothInstalled = App.shared.availablePhpVersions.contains(tapPhp.version)
|
||||||
|
&& App.shared.availablePhpVersions.contains(App.shared.brewPhpVersion)
|
||||||
|
|
||||||
|
if bothInstalled {
|
||||||
|
print("Both conflicting aliases seem to be installed, warning the user!")
|
||||||
|
} else {
|
||||||
|
print("Conflicting aliases are not both installed, seems fine!")
|
||||||
|
}
|
||||||
|
|
||||||
|
return bothInstalled
|
||||||
|
}
|
||||||
|
|
||||||
|
print("All seems to be OK. No conflicts, both are PHP \(tapPhp.version).")
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -7,12 +7,23 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
struct HomebrewPackage : Decodable {
|
struct HomebrewPackage: Decodable {
|
||||||
|
|
||||||
let name: String
|
let name: String
|
||||||
let full_name: String
|
let full_name: String
|
||||||
let aliases: [String]
|
let aliases: [String]
|
||||||
|
let installed: [HomebrewInstalled]
|
||||||
|
let linked_keg: String?
|
||||||
|
|
||||||
public var version: String {
|
public var version: String {
|
||||||
return aliases.first!.replacingOccurrences(of: "php@", with: "")
|
return aliases.first!.replacingOccurrences(of: "php@", with: "")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
struct HomebrewInstalled: Decodable {
|
||||||
|
let version: String
|
||||||
|
let built_as_bottle: Bool
|
||||||
|
let installed_as_dependency: Bool
|
||||||
|
let installed_on_request: Bool
|
||||||
}
|
}
|
@@ -10,6 +10,7 @@ import Foundation
|
|||||||
import Cocoa
|
import Cocoa
|
||||||
|
|
||||||
class HeaderView: NSView, XibLoadable {
|
class HeaderView: NSView, XibLoadable {
|
||||||
|
|
||||||
@IBOutlet weak var textField: NSTextField!
|
@IBOutlet weak var textField: NSTextField!
|
||||||
|
|
||||||
static func asMenuItem(text: String) -> NSMenuItem {
|
static func asMenuItem(text: String) -> NSMenuItem {
|
||||||
@@ -20,4 +21,5 @@ class HeaderView: NSView, XibLoadable {
|
|||||||
item.target = self
|
item.target = self
|
||||||
return item
|
return item
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -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.
|
||||||
*/
|
*/
|
||||||
@@ -39,9 +41,23 @@ class MainMenu: NSObject, NSWindowDelegate {
|
|||||||
When the environment is all clear and the app can run, let's go.
|
When the environment is all clear and the app can run, let's go.
|
||||||
*/
|
*/
|
||||||
private func onEnvironmentPass() {
|
private func onEnvironmentPass() {
|
||||||
App.shared.availablePhpVersions = Actions.detectPhpVersions()
|
_ = Actions.detectPhpVersions()
|
||||||
|
|
||||||
|
if HomebrewDiagnostics.shared.errors.contains(.aliasConflict) {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
Alert.notify(
|
||||||
|
message: "alert.php_alias_conflict.title".localized,
|
||||||
|
info: "alert.php_alias_conflict.info".localized,
|
||||||
|
style: .critical
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
updatePhpVersionInStatusBar()
|
updatePhpVersionInStatusBar()
|
||||||
|
|
||||||
|
let installation = App.phpInstall!
|
||||||
|
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 { [self] in
|
DispatchQueue.main.async { [self] in
|
||||||
App.shared.timer = Timer.scheduledTimer(
|
App.shared.timer = Timer.scheduledTimer(
|
||||||
@@ -96,7 +112,7 @@ 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_preferences".localized, action: #selector(openPrefs), keyEquivalent: ""))
|
menu.addItem(NSMenuItem(title: "mi_preferences".localized, action: #selector(openPrefs), keyEquivalent: ","))
|
||||||
menu.addItem(NSMenuItem(title: "mi_about".localized, action: #selector(openAbout), keyEquivalent: ""))
|
menu.addItem(NSMenuItem(title: "mi_about".localized, action: #selector(openAbout), keyEquivalent: ""))
|
||||||
menu.addItem(NSMenuItem(title: "mi_quit".localized, action: #selector(terminateApp), keyEquivalent: "q"))
|
menu.addItem(NSMenuItem(title: "mi_quit".localized, action: #selector(terminateApp), keyEquivalent: "q"))
|
||||||
|
|
||||||
@@ -106,6 +122,7 @@ class MainMenu: NSObject, NSWindowDelegate {
|
|||||||
})
|
})
|
||||||
|
|
||||||
statusItem.menu = menu
|
statusItem.menu = menu
|
||||||
|
statusItem.menu?.delegate = self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,7 +176,7 @@ class MainMenu: NSObject, NSWindowDelegate {
|
|||||||
// MARK: - User Interface
|
// MARK: - User Interface
|
||||||
|
|
||||||
@objc func updatePhpVersionInStatusBar() {
|
@objc func updatePhpVersionInStatusBar() {
|
||||||
App.shared.currentInstall = PhpInstallation()
|
App.shared.currentInstall = ActivePhpInstallation()
|
||||||
refreshIcon()
|
refreshIcon()
|
||||||
update()
|
update()
|
||||||
}
|
}
|
||||||
@@ -169,12 +186,13 @@ class MainMenu: NSObject, NSWindowDelegate {
|
|||||||
if (App.busy) {
|
if (App.busy) {
|
||||||
setStatusBar(image: NSImage(named: NSImage.Name("StatusBarIcon"))!)
|
setStatusBar(image: NSImage(named: NSImage.Name("StatusBarIcon"))!)
|
||||||
} else {
|
} else {
|
||||||
if Preferences.preferences[.shouldDisplayDynamicIcon] == false {
|
if Preferences.preferences[.shouldDisplayDynamicIcon] as! Bool == false {
|
||||||
// Static icon has been requested
|
// Static icon has been requested
|
||||||
setStatusBar(image: NSImage(named: NSImage.Name("StatusBarIconStatic"))!)
|
setStatusBar(image: NSImage(named: NSImage.Name("StatusBarIconStatic"))!)
|
||||||
} else {
|
} else {
|
||||||
// The dynamic icon has been requested
|
// The dynamic icon has been requested
|
||||||
setStatusBarImage(version: App.phpInstall!.version.short)
|
let long = Preferences.preferences[.fullPhpVersionDynamicIcon] as! Bool
|
||||||
|
setStatusBarImage(version: long ? App.phpInstall!.version.long : App.phpInstall!.version.short)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -212,6 +230,26 @@ class MainMenu: NSObject, NSWindowDelegate {
|
|||||||
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
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,6 +268,10 @@ class MainMenu: NSObject, NSWindowDelegate {
|
|||||||
@objc func toggleExtension(sender: ExtensionMenuItem) {
|
@objc func toggleExtension(sender: ExtensionMenuItem) {
|
||||||
waitAndExecute {
|
waitAndExecute {
|
||||||
sender.phpExtension?.toggle()
|
sender.phpExtension?.toggle()
|
||||||
|
|
||||||
|
if Preferences.preferences[.autoServiceRestartAfterExtensionToggle] as! Bool == true {
|
||||||
|
Actions.restartPhpFpm()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -269,43 +311,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()
|
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
|
||||||
updatePhpVersionInStatusBar()
|
updatePhpVersionInStatusBar()
|
||||||
|
|
||||||
// Update the menu
|
// Update the menu
|
||||||
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] 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)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -321,4 +370,16 @@ class MainMenu: NSObject, NSWindowDelegate {
|
|||||||
@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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -10,6 +10,7 @@ import Foundation
|
|||||||
import Cocoa
|
import Cocoa
|
||||||
|
|
||||||
class StatsView: NSView, XibLoadable {
|
class StatsView: NSView, XibLoadable {
|
||||||
|
|
||||||
@IBOutlet weak var titleMemLimit: NSTextField!
|
@IBOutlet weak var titleMemLimit: NSTextField!
|
||||||
@IBOutlet weak var titleMaxPost: NSTextField!
|
@IBOutlet weak var titleMaxPost: NSTextField!
|
||||||
@IBOutlet weak var titleMaxUpload: NSTextField!
|
@IBOutlet weak var titleMaxUpload: NSTextField!
|
||||||
@@ -31,4 +32,5 @@ class StatsView: NSView, XibLoadable {
|
|||||||
item.target = self
|
item.target = self
|
||||||
return item
|
return item
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -42,15 +42,24 @@ class StatusMenu : NSMenu {
|
|||||||
private func addSwitchToPhpMenuItems() {
|
private func addSwitchToPhpMenuItems() {
|
||||||
var shortcutKey = 1
|
var shortcutKey = 1
|
||||||
for index in (0..<App.shared.availablePhpVersions.count).reversed() {
|
for index in (0..<App.shared.availablePhpVersions.count).reversed() {
|
||||||
let version = App.shared.availablePhpVersions[index]
|
|
||||||
|
// Get the short and long version
|
||||||
|
let shortVersion = App.shared.availablePhpVersions[index]
|
||||||
|
let longVersion = App.shared.cachedPhpInstallations[shortVersion]!.longVersion
|
||||||
|
|
||||||
|
let long = Preferences.preferences[.fullPhpVersionDynamicIcon] as! Bool
|
||||||
|
let versionString = long ? longVersion : shortVersion
|
||||||
|
|
||||||
let action = #selector(MainMenu.switchToPhpVersion(sender:))
|
let action = #selector(MainMenu.switchToPhpVersion(sender:))
|
||||||
let brew = (version == App.shared.brewPhpVersion) ? "php" : "php@\(version)"
|
let brew = (shortVersion == App.shared.brewPhpVersion) ? "php" : "php@\(shortVersion)"
|
||||||
let menuItem = PhpMenuItem(
|
let menuItem = PhpMenuItem(
|
||||||
title: "\("mi_php_switch".localized) \(version) (\(brew))",
|
title: "\("mi_php_switch".localized) \(versionString) (\(brew))",
|
||||||
action: (version == App.phpInstall?.version.short) ? nil : action, keyEquivalent: "\(shortcutKey)"
|
action: (shortVersion == App.phpInstall?.version.short) ? nil : action, keyEquivalent: "\(shortcutKey)"
|
||||||
)
|
)
|
||||||
menuItem.version = version
|
|
||||||
|
menuItem.version = shortVersion
|
||||||
shortcutKey = shortcutKey + 1
|
shortcutKey = shortcutKey + 1
|
||||||
|
|
||||||
self.addItem(menuItem)
|
self.addItem(menuItem)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -58,11 +67,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 +94,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 +107,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,8 +120,10 @@ 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.separator())
|
||||||
@@ -115,11 +131,19 @@ class StatusMenu : NSMenu {
|
|||||||
self.addItem(NSMenuItem(title: "mi_php_refresh".localized, action: #selector(MainMenu.reloadPhpMonitorMenu), keyEquivalent: "r"))
|
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
|
||||||
|
|
||||||
|
174
phpmon/Domain/PHP/ActivePhpInstallation.swift
Normal file
174
phpmon/Domain/PHP/ActivePhpInstallation.swift
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
//
|
||||||
|
// ActivePhpInstallation.swift
|
||||||
|
// PHP Monitor
|
||||||
|
//
|
||||||
|
// Copyright © 2021 Nico Verbruggen. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
/**
|
||||||
|
An installed version of PHP, that was detected by scanning the `/opt/php@version/bin` directory.
|
||||||
|
|
||||||
|
When initialized, that version's .ini files are also scanned (for active or inactive extensions).
|
||||||
|
Integrity checks can be performed to determine whether PHP-FPM is configured correctly.
|
||||||
|
|
||||||
|
- Note: Each installation has a separate version number. Using `version.short` is advisable if you want to interact with Homebrew.
|
||||||
|
*/
|
||||||
|
class ActivePhpInstallation {
|
||||||
|
|
||||||
|
var version: Version!
|
||||||
|
var configuration: Configuration!
|
||||||
|
var extensions: [PhpExtension]!
|
||||||
|
|
||||||
|
// MARK: - Computed
|
||||||
|
|
||||||
|
var formula: String {
|
||||||
|
return (version.short == App.shared.brewPhpVersion) ? "php" : "php@\(version.short)"
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Initializer
|
||||||
|
|
||||||
|
init() {
|
||||||
|
// Show information about the current version
|
||||||
|
self.getVersion()
|
||||||
|
|
||||||
|
// If an error occurred, exit early
|
||||||
|
if (version.error) {
|
||||||
|
configuration = Configuration()
|
||||||
|
extensions = []
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load extension information
|
||||||
|
let path = URL(fileURLWithPath: "\(Paths.etcPath)/php/\(version.short)/php.ini")
|
||||||
|
extensions = PhpExtension.load(from: path)
|
||||||
|
|
||||||
|
// Get configuration values
|
||||||
|
configuration = Configuration(
|
||||||
|
memory_limit: self.getByteCount(key: "memory_limit"),
|
||||||
|
upload_max_filesize: self.getByteCount(key: "upload_max_filesize"),
|
||||||
|
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,
|
||||||
|
_or_ if the output contains the word "Warning" or "Error". In normal situations this should not be the case.
|
||||||
|
*/
|
||||||
|
private func getVersion() -> Void {
|
||||||
|
self.version = Version()
|
||||||
|
|
||||||
|
let version = Command.execute(path: Paths.phpConfig, arguments: ["--version"], trimNewlines: true)
|
||||||
|
|
||||||
|
if (version == "" || version.contains("Warning") || version.contains("Error")) {
|
||||||
|
self.version.short = "💩 BROKEN"
|
||||||
|
self.version.long = ""
|
||||||
|
self.version.error = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// That's the long version
|
||||||
|
self.version.long = version
|
||||||
|
|
||||||
|
// Next up, let's strip away the minor version number
|
||||||
|
let segments = self.version.long.components(separatedBy: ".")
|
||||||
|
|
||||||
|
// Get the first two elements
|
||||||
|
self.version.short = segments[0...1].joined(separator: ".")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Retrieves the display value for a specific key in the `.ini` file.
|
||||||
|
|
||||||
|
The following values are valid:
|
||||||
|
* -1: unlimited (show the infinity icon)
|
||||||
|
* 10000: an integer = amount of bytes
|
||||||
|
* 1K, 1M, 1G = shorthand for kilobytes, megabytes and gigabytes
|
||||||
|
|
||||||
|
If none of these notations are used, the _fallback_ value is used. We'll show an emoji to indicate something has gone wrong here.
|
||||||
|
To clarify, B gets appended to valid values. As a result, "5M" (valid) becomes "5MB", and "5MB" (invalid) becomes ⚠️.
|
||||||
|
|
||||||
|
- Parameter key: The key of the `ini` value that needs to be retrieved. For example, you can use `memory_limit`.
|
||||||
|
*/
|
||||||
|
private func getByteCount(key: String) -> String {
|
||||||
|
let value = Command.execute(path: Paths.php, arguments: ["-r", "echo ini_get('\(key)');"])
|
||||||
|
|
||||||
|
// Check if the value is unlimited
|
||||||
|
if (value == "-1") {
|
||||||
|
return "∞"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the syntax is valid otherwise
|
||||||
|
let regex = try! NSRegularExpression(pattern: #"^([0-9]*)(K|M|G|)$"#, options: [])
|
||||||
|
let match = regex.matches(in: value, options: [], range: NSMakeRange(0, value.count)).first
|
||||||
|
return (match == nil) ? "⚠️" : "\(value)B"
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
It is always possible that the system configuration for PHP-FPM has not been set up for Valet.
|
||||||
|
This can occur when a user manually installs a new PHP version, but does not run `valet install`.
|
||||||
|
In that case, we should alert the user!
|
||||||
|
|
||||||
|
- Important: The underlying check is `checkPhpFpmStatus`, which can be run multiple times.
|
||||||
|
This method actively presents a modal if said checks fails, so don't call this method too many times.
|
||||||
|
*/
|
||||||
|
public func notifyAboutBrokenPhpFpm() {
|
||||||
|
if !self.checkPhpFpmStatus() {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
Alert.notify(
|
||||||
|
message: "alert.php_fpm_broken.title".localized,
|
||||||
|
info: "alert.php_fpm_broken.info".localized,
|
||||||
|
style: .critical
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Determine if PHP-FPM is configured correctly.
|
||||||
|
|
||||||
|
For PHP 5.6, we'll check if `valet.sock` is included in the main `php-fpm.conf` file, but for more recent
|
||||||
|
versions of PHP, we can just check for the existence of the `valet-fpm.conf` file. If the check here fails,
|
||||||
|
that means that Valet won't work properly.
|
||||||
|
*/
|
||||||
|
private func checkPhpFpmStatus() -> Bool {
|
||||||
|
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
|
||||||
|
|
||||||
|
struct Version {
|
||||||
|
var short = "???"
|
||||||
|
var long = "???"
|
||||||
|
var error = false
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Configuration {
|
||||||
|
var memory_limit = "???"
|
||||||
|
var upload_max_filesize = "???"
|
||||||
|
var post_max_size = "???"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -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|;(\s?)extension|;(\s?)zend_extension)(\s?)(=)(\s?)(?<name>["]?(?:\/?.\/?)+(?:\.so)"?)$"#
|
||||||
|
|
||||||
/**
|
/**
|
||||||
When registering an extension, we do that based on the line found inside the .ini file.
|
When registering an extension, we do that based on the line found inside the .ini file.
|
||||||
@@ -52,7 +57,13 @@ 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
|
||||||
}
|
}
|
||||||
@@ -61,12 +72,15 @@ class PhpExtension {
|
|||||||
This simply toggles the extension in the .ini file. You may need to restart the other services in order for this change to apply.
|
This simply toggles the extension in the .ini file. You may need to restart the other services in order for this change to apply.
|
||||||
*/
|
*/
|
||||||
func toggle() {
|
func toggle() {
|
||||||
Actions.sed(
|
let newLine = enabled
|
||||||
file: file,
|
// DISABLED: Commented out line
|
||||||
original: line,
|
? "; \(line)"
|
||||||
replacement: enabled ? "; \(line)" : line.replacingOccurrences(of: "; ", with: "")
|
// ENABLED: Line where the comment delimiter (;) is removed
|
||||||
)
|
: line.replacingOccurrences(of: "; ", with: "")
|
||||||
enabled = !enabled
|
|
||||||
|
Actions.sed(file: file, original: line, replacement: newLine)
|
||||||
|
|
||||||
|
enabled.toggle()
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Static Methods
|
// MARK: - Static Methods
|
||||||
@@ -83,11 +97,12 @@ class PhpExtension {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return file!.components(separatedBy: "\n")
|
return file!.components(separatedBy: "\n")
|
||||||
.filter({ (line) -> Bool in
|
.filter {
|
||||||
return line.range(of: Self.extensionRegex, options: .regularExpression) != nil
|
return $0.range(of: Self.extensionRegex, options: .regularExpression) != nil
|
||||||
})
|
}
|
||||||
.map { (line) -> PhpExtension in
|
.map {
|
||||||
return PhpExtension(line, file: path.path)
|
return PhpExtension($0, file: path.path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
33
phpmon/Domain/PHP/PhpInstallation.swift
Normal file
33
phpmon/Domain/PHP/PhpInstallation.swift
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
//
|
||||||
|
// BrewPhpInstallation.swift
|
||||||
|
// PHP Monitor
|
||||||
|
//
|
||||||
|
// Created by Nico Verbruggen on 28/11/2021.
|
||||||
|
// Copyright © 2021 Nico Verbruggen. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
class PhpInstallation {
|
||||||
|
|
||||||
|
var longVersion: String
|
||||||
|
var homebrewInfo: HomebrewPackage
|
||||||
|
|
||||||
|
init(_ version: String) {
|
||||||
|
let phpConfigExecutablePath = "\(Paths.optPath)/php@\(version)/bin/php-config"
|
||||||
|
self.longVersion = version
|
||||||
|
if Shell.fileExists(phpConfigExecutablePath) {
|
||||||
|
self.longVersion = Command.execute(
|
||||||
|
path: phpConfigExecutablePath,
|
||||||
|
arguments: ["--version"]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
let info = Shell.pipe("\(Paths.brew) info php@\(version) --json")
|
||||||
|
self.homebrewInfo = try! JSONDecoder().decode(
|
||||||
|
[HomebrewPackage].self,
|
||||||
|
from: info.data(using: .utf8)!
|
||||||
|
).first!
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -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
|
||||||
|
}
|
||||||
|
}
|
@@ -9,42 +9,83 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
enum PreferenceName: String {
|
enum PreferenceName: String {
|
||||||
|
case wasLaunchedBefore = "launched_before"
|
||||||
case shouldDisplayDynamicIcon = "use_dynamic_icon"
|
case shouldDisplayDynamicIcon = "use_dynamic_icon"
|
||||||
|
case fullPhpVersionDynamicIcon = "full_php_in_menu_bar"
|
||||||
|
case autoServiceRestartAfterExtensionToggle = "auto_restart_after_extension_toggle"
|
||||||
|
case globalHotkey = "global_hotkey"
|
||||||
}
|
}
|
||||||
|
|
||||||
class Preferences {
|
class Preferences {
|
||||||
|
|
||||||
static func handleFirstTimeLaunch() {
|
// MARK: - Singleton
|
||||||
let launchedBefore = UserDefaults.standard.bool(forKey: "launched_before")
|
|
||||||
|
static var shared = Preferences()
|
||||||
if launchedBefore {
|
|
||||||
return
|
var cachedPreferences: [PreferenceName: Any?]
|
||||||
}
|
|
||||||
|
public init() {
|
||||||
UserDefaults.standard.setValue(true, forKey: PreferenceName.shouldDisplayDynamicIcon.rawValue)
|
Preferences.handleFirstTimeLaunch()
|
||||||
UserDefaults.standard.setValue(true, forKey: "launched_before")
|
self.cachedPreferences = Self.cache()
|
||||||
UserDefaults.standard.synchronize()
|
|
||||||
|
|
||||||
print("Saving first-time preferences!")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static func retrieve() -> [PreferenceName: Bool] {
|
// MARK: - First Time Run
|
||||||
Preferences.handleFirstTimeLaunch()
|
|
||||||
|
/**
|
||||||
|
Note: macOS seems to cache plist values in memory as well as in files.
|
||||||
|
You can find the persisted configuration file in: ~/Library/Preferences/com.nicoverbruggen.phpmon.plist
|
||||||
|
|
||||||
|
To clear the cache, and get a first-run experience you may need to run:
|
||||||
|
```
|
||||||
|
defaults delete com.nicoverbruggen.phpmon
|
||||||
|
killall cfprefsd
|
||||||
|
```
|
||||||
|
*/
|
||||||
|
static func handleFirstTimeLaunch() {
|
||||||
|
UserDefaults.standard.register(defaults: [
|
||||||
|
PreferenceName.shouldDisplayDynamicIcon.rawValue: true,
|
||||||
|
PreferenceName.fullPhpVersionDynamicIcon.rawValue: false,
|
||||||
|
PreferenceName.autoServiceRestartAfterExtensionToggle.rawValue: true
|
||||||
|
])
|
||||||
|
|
||||||
|
if UserDefaults.standard.bool(forKey: PreferenceName.wasLaunchedBefore.rawValue) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
print("Saving first-time preferences!")
|
||||||
|
UserDefaults.standard.setValue(true, forKey: PreferenceName.wasLaunchedBefore.rawValue)
|
||||||
|
UserDefaults.standard.synchronize()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - API
|
||||||
|
|
||||||
|
static var preferences: [PreferenceName: Any?] {
|
||||||
|
return Self.shared.cachedPreferences
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Internal Functionality
|
||||||
|
|
||||||
|
static func cache() -> [PreferenceName: Any] {
|
||||||
return [
|
return [
|
||||||
.shouldDisplayDynamicIcon: UserDefaults.standard.bool(
|
// Part 1: Always Booleans
|
||||||
forKey: PreferenceName.shouldDisplayDynamicIcon.rawValue
|
.shouldDisplayDynamicIcon: UserDefaults.standard.bool(forKey: PreferenceName.shouldDisplayDynamicIcon.rawValue) as Any,
|
||||||
)
|
.fullPhpVersionDynamicIcon: UserDefaults.standard.bool(forKey: PreferenceName.fullPhpVersionDynamicIcon.rawValue) as Any,
|
||||||
|
.autoServiceRestartAfterExtensionToggle: UserDefaults.standard.bool(forKey: PreferenceName.autoServiceRestartAfterExtensionToggle.rawValue) as Any,
|
||||||
|
|
||||||
|
// Part 2: Always Strings
|
||||||
|
.globalHotkey: UserDefaults.standard.string(forKey: PreferenceName.globalHotkey.rawValue) as Any,
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
static var preferences: [PreferenceName: Bool] {
|
static func update(_ preference: PreferenceName, value: Any?) {
|
||||||
return Preferences.retrieve()
|
if (value == nil) {
|
||||||
}
|
UserDefaults.standard.removeObject(forKey: preference.rawValue)
|
||||||
|
} else {
|
||||||
static func update(_ preference: PreferenceName, value: Bool) {
|
UserDefaults.standard.setValue(value, forKey: preference.rawValue)
|
||||||
UserDefaults.standard.setValue(value, forKey: preference.rawValue)
|
}
|
||||||
UserDefaults.standard.synchronize()
|
UserDefaults.standard.synchronize()
|
||||||
|
|
||||||
|
// Update the preferences cache in memory!
|
||||||
|
Preferences.shared.cachedPreferences = Preferences.cache()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -7,44 +7,222 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import Cocoa
|
import Cocoa
|
||||||
|
import HotKey
|
||||||
|
import Carbon
|
||||||
|
|
||||||
class PrefsVC: NSViewController {
|
class PrefsVC: NSViewController {
|
||||||
|
|
||||||
|
// Labels on the left
|
||||||
|
@IBOutlet weak var leftLabelDynamicIcon: NSTextField!
|
||||||
|
@IBOutlet weak var leftLabelServices: NSTextField!
|
||||||
|
@IBOutlet weak var leftLabelGlobalShortcut: NSTextField!
|
||||||
|
|
||||||
|
// Dynamic icon
|
||||||
@IBOutlet weak var buttonDynamicIcon: NSButton!
|
@IBOutlet weak var buttonDynamicIcon: NSButton!
|
||||||
@IBOutlet weak var labelDynamicIcon: NSTextField!
|
@IBOutlet weak var labelDynamicIcon: NSTextField!
|
||||||
|
|
||||||
|
// Full PHP version
|
||||||
|
@IBOutlet weak var buttonDisplayFullPhpVersion: NSButton!
|
||||||
|
@IBOutlet weak var labelDisplayFullPhpVersion: NSTextField!
|
||||||
|
|
||||||
|
// Auto-restart services
|
||||||
|
@IBOutlet weak var buttonAutoRestartServices: NSButton!
|
||||||
|
@IBOutlet weak var labelAutoRestartServices: NSTextField!
|
||||||
|
|
||||||
|
// Shortcut
|
||||||
|
@IBOutlet weak var buttonSetShortcut: NSButton!
|
||||||
|
@IBOutlet weak var buttonClearShortcut: NSButton!
|
||||||
|
@IBOutlet weak var labelShortcut: NSTextField!
|
||||||
|
|
||||||
|
// Close button (bottom right)
|
||||||
@IBOutlet weak var buttonClose: NSButton!
|
@IBOutlet weak var buttonClose: NSButton!
|
||||||
|
|
||||||
|
// MARK: - Display
|
||||||
|
|
||||||
public static func show(delegate: NSWindowDelegate? = nil) {
|
public static func show(delegate: NSWindowDelegate? = nil) {
|
||||||
if (App.shared.windowController == nil) {
|
if (App.shared.windowController == nil) {
|
||||||
let vc = NSStoryboard(name: "Main", bundle: nil).instantiateController(withIdentifier: "preferences") as! PrefsVC
|
let vc = NSStoryboard(name: "Main", bundle: nil)
|
||||||
|
.instantiateController(withIdentifier: "preferences") as! PrefsVC
|
||||||
let window = NSWindow(contentViewController: vc)
|
let window = NSWindow(contentViewController: vc)
|
||||||
|
|
||||||
window.title = "prefs.title".localized
|
window.title = "prefs.title".localized
|
||||||
window.delegate = delegate
|
window.delegate = delegate
|
||||||
window.styleMask = [.titled, .closable]
|
window.styleMask = [.titled, .closable]
|
||||||
App.shared.windowController = NSWindowController(window: window)
|
|
||||||
|
App.shared.windowController = PrefsWC(window: window)
|
||||||
}
|
}
|
||||||
|
|
||||||
App.shared.windowController!.showWindow(self)
|
App.shared.windowController!.showWindow(self)
|
||||||
NSApp.activate(ignoringOtherApps: true)
|
NSApp.activate(ignoringOtherApps: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Lifecycle
|
||||||
|
|
||||||
|
override func viewWillAppear() {
|
||||||
|
loadLocalization()
|
||||||
|
loadDynamicIconFromPreferences()
|
||||||
|
loadFullPhpVersionFromPreferences()
|
||||||
|
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
|
||||||
|
|
||||||
|
// Full PHP version
|
||||||
|
buttonDisplayFullPhpVersion.title = "prefs.display_full_php_version".localized
|
||||||
|
labelDisplayFullPhpVersion.stringValue = "prefs.display_full_php_version_desc".localized
|
||||||
|
|
||||||
|
// Services
|
||||||
|
leftLabelServices.stringValue = "prefs.services".localized
|
||||||
|
buttonAutoRestartServices.title = "prefs.auto_restart_services_title".localized
|
||||||
|
labelAutoRestartServices.stringValue = "prefs_auto_restart_services_desc".localized
|
||||||
|
|
||||||
|
// Global Shortcut
|
||||||
|
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: - Loading Preferences
|
||||||
|
|
||||||
|
func loadDynamicIconFromPreferences() {
|
||||||
|
let shouldDisplay = Preferences.preferences[.shouldDisplayDynamicIcon] as! Bool == true
|
||||||
|
self.buttonDynamicIcon.state = shouldDisplay ? .on : .off
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadFullPhpVersionFromPreferences() {
|
||||||
|
let shouldDisplay = Preferences.preferences[.fullPhpVersionDynamicIcon] as! Bool == true
|
||||||
|
self.buttonDisplayFullPhpVersion.state = shouldDisplay ? .on : .off
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadAutoRestartServicesFromPreferences() {
|
||||||
|
let shouldDisplay = Preferences.preferences[.autoServiceRestartAfterExtensionToggle] as! Bool == true
|
||||||
|
self.buttonAutoRestartServices.state = shouldDisplay ? .on : .off
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Actions
|
||||||
|
|
||||||
@IBAction func toggledDynamicIcon(_ sender: Any) {
|
@IBAction func toggledDynamicIcon(_ sender: Any) {
|
||||||
Preferences.update(.shouldDisplayDynamicIcon, value: buttonDynamicIcon.state == .on)
|
Preferences.update(.shouldDisplayDynamicIcon, value: buttonDynamicIcon.state == .on)
|
||||||
MainMenu.shared.refreshIcon()
|
MainMenu.shared.refreshIcon()
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewWillAppear() {
|
@IBAction func toggledFullPhpVersion(_ sender: Any) {
|
||||||
buttonDynamicIcon.title = "prefs.dynamic_icon_title".localized
|
Preferences.update(.fullPhpVersionDynamicIcon, value: buttonDisplayFullPhpVersion.state == .on)
|
||||||
labelDynamicIcon.stringValue = "prefs.dynamic_icon_desc".localized
|
MainMenu.shared.refreshIcon()
|
||||||
buttonClose.title = "prefs.close".localized
|
MainMenu.shared.update()
|
||||||
|
}
|
||||||
|
|
||||||
|
@IBAction func toggledAutoRestartServices(_ sender: Any) {
|
||||||
|
Preferences.update(.autoServiceRestartAfterExtensionToggle, value: buttonAutoRestartServices.state == .on)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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?)
|
||||||
|
|
||||||
let prefs = Preferences.preferences
|
if (globalKeybind != nil) {
|
||||||
self.buttonDynamicIcon.state = (prefs[.shouldDisplayDynamicIcon] == true) ? .on : .off
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func pressed(_ sender: Any) {
|
@IBAction func pressed(_ sender: Any) {
|
||||||
self.view.window?.windowController?.close()
|
self.view.window?.windowController?.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Deinitialization
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
print("VC deallocated")
|
print("VC deallocated")
|
||||||
}
|
}
|
||||||
|
37
phpmon/Domain/Preferences/PrefsWC.swift
Normal file
37
phpmon/Domain/Preferences/PrefsWC.swift
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
//
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -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,7 +27,14 @@ 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)!
|
||||||
return output;
|
|
||||||
|
if (trimNewlines) {
|
||||||
|
return output.components(separatedBy: .newlines)
|
||||||
|
.filter({ !$0.isEmpty })
|
||||||
|
.joined(separator: "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
return output
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -47,6 +47,10 @@ class Paths {
|
|||||||
return "\(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 {
|
||||||
@@ -60,4 +64,5 @@ class Paths {
|
|||||||
public static var etcPath: String {
|
public static var etcPath: String {
|
||||||
return "\(shared.baseDir.rawValue)/etc"
|
return "\(shared.baseDir.rawValue)/etc"
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -16,12 +16,12 @@ 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
|
||||||
|
|
||||||
var shell = "/bin/sh"
|
var shell: String
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
// Determine if we're using macOS Catalina or newer (that support /bin/zsh as default shell)
|
// Determine if we're using macOS Catalina or newer (that support /bin/zsh as default shell)
|
||||||
@@ -29,9 +29,14 @@ class Shell {
|
|||||||
.init(majorVersion: 10, minorVersion: 15, patchVersion: 0))
|
.init(majorVersion: 10, minorVersion: 15, patchVersion: 0))
|
||||||
|
|
||||||
// If macOS Mojave is being used, we'll default to /bin/bash
|
// If macOS Mojave is being used, we'll default to /bin/bash
|
||||||
shell = at_least_10_15 ? "/bin/sh" : "/bin/bash"
|
shell = at_least_10_15
|
||||||
print(at_least_10_15 ? "Detected recent macOS (> 10.15): defaulting to /bin/sh"
|
? "/bin/sh"
|
||||||
: "Detected older macOS (< 10.15): so defaulting to /bin/bash")
|
: "/bin/bash"
|
||||||
|
|
||||||
|
print(at_least_10_15
|
||||||
|
? "Detected recent macOS (> 10.15): defaulting to /bin/sh"
|
||||||
|
: "Detected older macOS (< 10.15): defaulting to /bin/bash"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -58,25 +63,31 @@ class Shell {
|
|||||||
*/
|
*/
|
||||||
func pipe(_ command: String) -> String {
|
func pipe(_ command: String) -> String {
|
||||||
let task = Process()
|
let task = Process()
|
||||||
let pipe = Pipe()
|
let outputPipe = Pipe()
|
||||||
|
let errorPipe = Pipe()
|
||||||
|
|
||||||
task.launchPath = self.shell
|
task.launchPath = self.shell
|
||||||
task.arguments = ["--login", "-c", command]
|
task.arguments = ["--login", "-c", command]
|
||||||
task.standardOutput = pipe
|
task.standardOutput = outputPipe
|
||||||
|
task.standardError = errorPipe
|
||||||
task.launch()
|
task.launch()
|
||||||
|
|
||||||
|
let error = String(data: errorPipe.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8)!
|
||||||
|
let output = String(data: outputPipe.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8)!
|
||||||
|
|
||||||
|
if (output == "" && error.lengthOfBytes(using: .utf8) > 0) {
|
||||||
|
return error
|
||||||
|
}
|
||||||
|
|
||||||
return String(
|
return output
|
||||||
data: pipe.fileHandleForReading.readDataToEndOfFile(),
|
|
||||||
encoding: .utf8
|
|
||||||
)!
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Checks if a file exists at the provided path.
|
Checks if a file exists at the provided path.
|
||||||
|
Uses `/bin/echo` instead of the `builtin` (which does not support `-n`).
|
||||||
*/
|
*/
|
||||||
public static func fileExists(_ path: String) -> Bool {
|
public static func fileExists(_ path: String) -> Bool {
|
||||||
return Shell.pipe(
|
return Shell.pipe("if [ -f \(path) ]; then /bin/echo -n \"0\"; fi") == "0"
|
||||||
"if [ -f \(path) ]; then echo \"PHP_Y_FE\"; fi"
|
|
||||||
).contains("PHP_Y_FE")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -22,8 +22,9 @@
|
|||||||
"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_php_refresh" = "Refresh information";
|
||||||
|
|
||||||
@@ -35,6 +36,7 @@
|
|||||||
|
|
||||||
"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.";
|
||||||
@@ -45,15 +47,40 @@
|
|||||||
|
|
||||||
// PREFERENCES
|
// PREFERENCES
|
||||||
|
|
||||||
"prefs.title" = "Preferences";
|
"prefs.title" = "PHP Monitor";
|
||||||
"prefs.close" = "Close";
|
"prefs.close" = "Close";
|
||||||
"prefs.dynamic_icon_title" = "Show a dynamic icon in the 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 currently linked PHP version.";
|
"prefs.global_shortcut" = "Global shortcut:";
|
||||||
|
"prefs.dynamic_icon" = "Dynamic icon:";
|
||||||
|
"prefs.services" = "Services:";
|
||||||
|
|
||||||
|
"prefs.auto_restart_services_title" = "Auto-restart PHP-FPM";
|
||||||
|
"prefs_auto_restart_services_desc" = "When checked, will automatically restart PHP-FPM when\nyou check or uncheck an extension. Slightly slower when enabled, \nbut this applies the extension change immediately for all sites \nyou're serving, no need to restart PHP-FPM manually.";
|
||||||
|
|
||||||
|
"prefs.dynamic_icon_title" = "Display dynamic icon in menu bar";
|
||||||
|
"prefs.dynamic_icon_desc" = "If you uncheck this box, the truck icon will always be visible.\nIf checked, it will display the major version number of the\ncurrently linked PHP version.";
|
||||||
|
|
||||||
|
"prefs.display_full_php_version" = "Display full PHP version in menu bar";
|
||||||
|
"prefs.display_full_php_version_desc" = "Display the full version instead of the major version only.\n(This may be undesirable on smaller displays,\nso this is disabled by default.)";
|
||||||
|
|
||||||
|
"prefs.shortcut_set" = "Set global shortcut";
|
||||||
|
"prefs.shortcut_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 switched to PHP %@.";
|
||||||
|
|
||||||
|
"notification.php_fpm_restarted" = "PHP-FPM automatically restarted";
|
||||||
|
"notification.php_fpm_restarted_desc" = "You toggled an extension, so PHP-FPM was automatically restarted.";
|
||||||
|
|
||||||
|
"notification.services_stopped" = "Valet services stopped";
|
||||||
|
"notification.services_stopped_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
|
||||||
|
|
||||||
@@ -65,12 +92,20 @@
|
|||||||
"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.";
|
||||||
"alert.cannot_start.close" = "Close";
|
"alert.cannot_start.close" = "Close";
|
||||||
"alert.cannot_start.retry" = "Retry";
|
"alert.cannot_start.retry" = "Retry";
|
||||||
|
|
||||||
|
// PHP alias issue
|
||||||
|
"alert.php_alias_conflict.title" = "Homebrew `php` formula alias conflict detected";
|
||||||
|
"alert.php_alias_conflict.info" = "PHP Monitor has detected conflicting `php` aliases in your Homebrew setup, both of which have been detected as installed.\n\nThis will likely result in failed linking when switching PHP versions, and will break PHP Monitor functionality.\n\nFor more information, please visit: https://github.com/nicoverbruggen/phpmon/issues/54";
|
||||||
|
|
||||||
// STARTUP
|
// STARTUP
|
||||||
|
|
||||||
/// 1. PHP binary not found
|
/// 1. PHP binary not found
|
||||||
|
Reference in New Issue
Block a user