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

Compare commits

..

32 Commits
v5.0 ... v5.0.1

Author SHA1 Message Date
2e58bbb5e8 🚀 Version 5.0.1
🔀 Merge branch 'dev/5.x'
2022-02-08 23:40:10 +01:00
bb6abc5e9a 👌 Fix print statements 2022-02-08 23:37:17 +01:00
e4f9d491c3 🔥 Cleanup .afdesign file 2022-02-08 23:29:55 +01:00
1ba7ee37db 👌 Ensure target ownership is OK 2022-02-08 23:20:48 +01:00
34cfbae3a9 👌 Fix inconsistent capitalisation 2022-02-08 23:12:55 +01:00
9fabda545f ♻️ Big Cleanup 2022-02-08 23:07:23 +01:00
7cd50aed7b 🐛 Fix retain cycle due to threading issue 2022-02-08 22:19:32 +01:00
cdc082071d ♻️ Menu item method reordering 2022-02-08 21:52:17 +01:00
d1479672c8 ♻️ Rework site list loading UI
* 0.2 second delay for smooth UI (minimum)
* > 0.5 second delay for operation = show spinner
2022-02-08 21:51:59 +01:00
ffb112cfb2 🚛 Reorganise Errors structs 2022-02-08 21:26:06 +01:00
f5af33c098 🐛 Correctly parse RC PHP version (#132)
It'll be a while before a new release candidate is available, but this
bug has now been resolved.

A new `PhpVersionNumber.parse` method has been added which can throw.

The `VersionExtractor` class is now capable of extracting version
numbers from all strings now too, and isn't just used to determine
the Valet version number.

New tests have been added to handle these scenarios.

This commit also removes the phpmon-cli component, which wasn't being
updated or maintained (it was an experiment).
2022-02-08 18:51:16 +01:00
0f464f5814 🔥 Cleanup 2022-02-07 18:00:35 +01:00
776c2095e6 🚛 Move files around 2022-02-07 17:38:44 +01:00
82a2833161 👌 Improve separators (#128) 2022-02-07 17:19:57 +01:00
b14d7bdf07 Add multiple options for icons (#106)
- PHP next to icon (default)
- PHP Elephant
- No icon

If you chose "no PHP text next to icon" earlier, that preference is
remembered and migrated over.
2022-02-07 00:46:00 +01:00
ffa22eea25 👌 Cleanup, ensure all tests pass 2022-02-06 17:56:16 +01:00
8220e6409d 🚛 Move async helpers to MainMenu+Async 2022-02-06 13:18:12 +01:00
927e1c02fa ♻️ Refactor waitAndExecuteasyncExecution
This change allows for errors to be thrown during the `asyncExecution`
initial callback, and if one is thrown, allows a `failure` callback to
be used. Existing instances of `waitAndExecute` have been replaced.

I also added the ability to tweak the behaviours of the actions that are
always performed when the asyncExecution method is called: you can now
specify limited behaviours (e.g. only set busy icon). For that use case
I have already created a new method: `asyncWithBusyUI`.

With this change, the handling of the Homebrew Permissions flow has also
been modified: when the user does not get administrative permissions
an error is now thrown which results in an alert being presented to
the user once the error occurs.

There is now an opportunity to further refactor other parts of the app
to more gracefully handle failure states using the Error and
AlertableError protocols.
2022-02-06 13:15:03 +01:00
a13a17b106 👌 Various improvements
- Properly draw the menu bar icon
  (now with extra documentation)
- Resolve Paths via FileManager.default
- Updated DEV readme
- Add PHP Elephant icon (TBD)
2022-02-05 00:42:10 +01:00
18d6d73f94 🐛 Fix for #124 ('valet --version' failing) 2022-02-04 20:17:10 +01:00
54d101acbe 🍱 Updated icons file (Affinity) 2022-02-04 18:29:52 +01:00
76f720cb71 📝 Updated README 2022-02-04 18:24:56 +01:00
033e485ce1 🔥 Cleanup icons 2022-02-04 18:20:56 +01:00
82ee830de1 🔧 New dev build for bugfixing purposes 2022-02-04 18:20:24 +01:00
55cd1cccee 🐛 Do not watch files that do not exist (#123) 2022-02-04 18:10:30 +01:00
5f13ba3d1b 📝 Add section on symbolication 2022-02-04 17:54:30 +01:00
298aa0aa8d 🔧 Updated project 2022-02-04 17:30:41 +01:00
5d49be6f7e 🍱 Updated assets (#120) 2022-02-04 17:30:22 +01:00
ab81bf5875 📝 Updated FUNDING.yml 2022-02-04 15:35:53 +01:00
5d8765683a 🔧 Use beta icon for 5.1 betas 2022-02-03 20:38:02 +01:00
6c22bc0145 🏗 Fix Homebrew Permissions (#86) 2022-02-03 20:34:57 +01:00
5c1908668f ✏️ Adjust path in composer symlink recommendation (#115) 2022-02-03 17:07:22 +01:00
99 changed files with 1130 additions and 855 deletions

3
.github/FUNDING.yml vendored
View File

@ -1 +1,2 @@
custom: ['https://nicoverbruggen.be/sponsor', 'https://paypal.me/nicoverbruggen']
github: nicoverbruggen
custom: ['https://nicoverbruggen.be/sponsor']

46
DEVELOPER.md Normal file
View File

@ -0,0 +1,46 @@
# DEVELOPER README
## 🔧 Build instructions
<img src="./docs/build.png" width="404px" alt="build button in Xcode"/>
If you'd like to build PHP Monitor yourself, you need:
* Xcode (usually the latest version)
* The contents of this repository
Once you have downloaded this repository, open `PHP Monitor.xcodeproj`, and you should be able to immediately build the app for your system by pressing Cmd-R. This will create a debug build. (If Xcode complains about code signing, you can turn it off.)
If you'd like to create a production build, choose "Any Mac" as the target and select Product > Archive.
## 🐛 Symbolication of crashes
If you have an archived build of the app and exported the DSYM, it is possible to symbolicate .ips crash logs.
For example, given the following crash (from an .ips file):
```
Thread 2 Crashed:: Dispatch queue: com.apple.root.user-initiated-qos
0 libswiftDispatch.dylib 0x7ff82aa3ab8c static OS_dispatch_source.makeProcessSource(identifier:eventMask:queue:) + 28
1 PHP Monitor 0x1096907d8 0x10965e000 + 206808
| |
address load address
2 PHP Monitor 0x1096903ac 0x10965e000 + 205740
3 PHP Monitor 0x10968f88b 0x10965e000 + 202891
```
You must use the correct order for the the address and load address in the command below:
```
$ atos -arch x86_64 -o '/path/to/PHP Monitor.app.dSYM/Contents/Resources/DWARF/PHP Monitor' -l 0x10965e000 0x1096907d8
| | | |
architecture path to DSYM load address address
```
This will return the relevant information, for example:
```
FSWatcher.startMonitoring(_:behaviour:) (in PHP Monitor) (PhpConfigWatcher.swift:95)
```
For more information, see [Apple's documentation](https://developer.apple.com/documentation/xcode/adding-identifiable-symbol-names-to-a-crash-report).

View File

@ -9,13 +9,11 @@
/* Begin PBXBuildFile section */
5420395926135DC100FB00FA /* PrefsVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5420395826135DC100FB00FA /* PrefsVC.swift */; };
5420395F2613607600FB00FA /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5420395E2613607600FB00FA /* Preferences.swift */; };
54AB03262763858F00A29D5F /* Timer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54AB03252763858F00A29D5F /* Timer.swift */; };
54AB03272763858F00A29D5F /* Timer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54AB03252763858F00A29D5F /* Timer.swift */; };
54B48B5F275F66AE006D90C5 /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B48B5E275F66AE006D90C5 /* Application.swift */; };
54B48B60275F66AE006D90C5 /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B48B5E275F66AE006D90C5 /* Application.swift */; };
54EAC806262F212B0092D14E /* GlobalKeybindPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41CD0282628D8EE0065BBED /* GlobalKeybindPreference.swift */; };
54FCFD26276C883F004CE748 /* CheckboxPreferenceView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 54FCFD25276C883F004CE748 /* CheckboxPreferenceView.xib */; };
54FCFD27276C883F004CE748 /* CheckboxPreferenceView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 54FCFD25276C883F004CE748 /* CheckboxPreferenceView.xib */; };
54FCFD26276C883F004CE748 /* SelectPreferenceView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 54FCFD25276C883F004CE748 /* SelectPreferenceView.xib */; };
54FCFD27276C883F004CE748 /* SelectPreferenceView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 54FCFD25276C883F004CE748 /* SelectPreferenceView.xib */; };
54FCFD2A276C8AA4004CE748 /* CheckboxPreferenceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54FCFD29276C8AA4004CE748 /* CheckboxPreferenceView.swift */; };
54FCFD2B276C8AA4004CE748 /* CheckboxPreferenceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54FCFD29276C8AA4004CE748 /* CheckboxPreferenceView.swift */; };
54FCFD2D276C8D67004CE748 /* HotkeyPreferenceView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 54FCFD2C276C8D67004CE748 /* HotkeyPreferenceView.xib */; };
@ -24,37 +22,31 @@
54FCFD31276C8DA4004CE748 /* HotkeyPreferenceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54FCFD2F276C8DA4004CE748 /* HotkeyPreferenceView.swift */; };
C405A4D024B9B9140062FAFA /* InternetAccessPolicy.strings in Resources */ = {isa = PBXBuildFile; fileRef = C405A4CE24B9B9130062FAFA /* InternetAccessPolicy.strings */; };
C405A4D124B9B9140062FAFA /* InternetAccessPolicy.plist in Resources */ = {isa = PBXBuildFile; fileRef = C405A4CF24B9B9140062FAFA /* InternetAccessPolicy.plist */; };
C4068CA427B0780A00544CD5 /* CheckboxPreferenceView.xib in Resources */ = {isa = PBXBuildFile; fileRef = C4068CA327B0780A00544CD5 /* CheckboxPreferenceView.xib */; };
C4068CA527B0780A00544CD5 /* CheckboxPreferenceView.xib in Resources */ = {isa = PBXBuildFile; fileRef = C4068CA327B0780A00544CD5 /* CheckboxPreferenceView.xib */; };
C4068CA727B07A1300544CD5 /* SelectPreferenceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4068CA627B07A1300544CD5 /* SelectPreferenceView.swift */; };
C4068CA827B07A1300544CD5 /* SelectPreferenceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4068CA627B07A1300544CD5 /* SelectPreferenceView.swift */; };
C4068CAA27B0890D00544CD5 /* MenuBarIcons.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4068CA927B0890D00544CD5 /* MenuBarIcons.swift */; };
C4068CAB27B0890D00544CD5 /* MenuBarIcons.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4068CA927B0890D00544CD5 /* MenuBarIcons.swift */; };
C40B24F127A3106D0018C7D2 /* ServicesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EC1E67279DE0540010F296 /* ServicesView.swift */; };
C40B24F227A310770018C7D2 /* Events.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EC1E72279DFCF40010F296 /* Events.swift */; };
C40B24F327A310780018C7D2 /* Events.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EC1E72279DFCF40010F296 /* Events.swift */; };
C40B24F427A310830018C7D2 /* StatusMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47331A1247093B7009A0597 /* StatusMenu.swift */; };
C40B24F527A3108B0018C7D2 /* Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EC1E6C279DF87A0010F296 /* Async.swift */; };
C40C7F1E2772136000DDDCDC /* PhpEnv.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C7F1D2772136000DDDCDC /* PhpEnv.swift */; };
C40C7F1F2772136000DDDCDC /* PhpEnv.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C7F1D2772136000DDDCDC /* PhpEnv.swift */; };
C40C7F202772136000DDDCDC /* PhpEnv.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C7F1D2772136000DDDCDC /* PhpEnv.swift */; };
C40C7F2227721F8200DDDCDC /* PhpInstallation.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F2E4392752F7D00020E974 /* PhpInstallation.swift */; };
C40C7F2327721F8200DDDCDC /* ActivePhpInstallation.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4A22B019FF00E7CF16 /* ActivePhpInstallation.swift */; };
C40C7F2427721F8200DDDCDC /* PhpExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4ACA38E25C754C100060C66 /* PhpExtension.swift */; };
C40C7F2527721F9800DDDCDC /* HomebrewPackage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C412E5FB25700D5300A1FB67 /* HomebrewPackage.swift */; };
C40C7F2627721FA200DDDCDC /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EE188322D3386B00E126E5 /* Constants.swift */; };
C40C7F2827721FF600DDDCDC /* ActivePhpInstallation+Checks.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C7F2727721FF600DDDCDC /* ActivePhpInstallation+Checks.swift */; };
C40C7F2927721FF600DDDCDC /* ActivePhpInstallation+Checks.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C7F2727721FF600DDDCDC /* ActivePhpInstallation+Checks.swift */; };
C40C7F2B2772201C00DDDCDC /* Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C415D3B62770F294005EF286 /* Actions.swift */; };
C40C7F3027722E8D00DDDCDC /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C7F2F27722E8D00DDDCDC /* Logger.swift */; };
C40C7F3127722E8D00DDDCDC /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C7F2F27722E8D00DDDCDC /* Logger.swift */; };
C40C7F3227722E8D00DDDCDC /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C7F2F27722E8D00DDDCDC /* Logger.swift */; };
C412E5FC25700D5300A1FB67 /* HomebrewPackage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C412E5FB25700D5300A1FB67 /* HomebrewPackage.swift */; };
C415937F27A1B54F00D2E1B7 /* PhpFrameworks.swift in Sources */ = {isa = PBXBuildFile; fileRef = C415937E27A1B54F00D2E1B7 /* PhpFrameworks.swift */; };
C415938027A1B54F00D2E1B7 /* PhpFrameworks.swift in Sources */ = {isa = PBXBuildFile; fileRef = C415937E27A1B54F00D2E1B7 /* PhpFrameworks.swift */; };
C415D3B72770F294005EF286 /* Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C415D3B62770F294005EF286 /* Actions.swift */; };
C415D3B82770F294005EF286 /* Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C415D3B62770F294005EF286 /* Actions.swift */; };
C415D3E12770F34D005EF286 /* AllowedArguments.swift in Sources */ = {isa = PBXBuildFile; fileRef = C415D3DE2770F34D005EF286 /* AllowedArguments.swift */; };
C415D3E62770F540005EF286 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = C415D3E52770F540005EF286 /* main.swift */; };
C415D3E82770F692005EF286 /* AppDelegate+InterApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = C415D3E72770F692005EF286 /* AppDelegate+InterApp.swift */; };
C415D3E92770F692005EF286 /* AppDelegate+InterApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = C415D3E72770F692005EF286 /* AppDelegate+InterApp.swift */; };
C417DC74277614690015E6EE /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = C417DC73277614690015E6EE /* Helpers.swift */; };
C417DC75277614690015E6EE /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = C417DC73277614690015E6EE /* Helpers.swift */; };
C417DC76277614690015E6EE /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = C417DC73277614690015E6EE /* Helpers.swift */; };
C4188989275FE8CB001EF227 /* Filesystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4188988275FE8CB001EF227 /* Filesystem.swift */; };
C418898A275FE8CB001EF227 /* Filesystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4188988275FE8CB001EF227 /* Filesystem.swift */; };
C41C1B3722B0097F00E7CF16 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B3622B0097F00E7CF16 /* AppDelegate.swift */; };
@ -79,6 +71,10 @@
C44C198E276E3A1C0072762D /* ProgressWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44C198C276E3A1C0072762D /* ProgressWindow.swift */; };
C44C1991276E44CB0072762D /* ProgressWindow.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C44C1990276E44CB0072762D /* ProgressWindow.storyboard */; };
C44C1992276E44CB0072762D /* ProgressWindow.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C44C1990276E44CB0072762D /* ProgressWindow.storyboard */; };
C44CCD4027AFE2FC00CE40E5 /* AlertableError.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44CCD3F27AFE2FC00CE40E5 /* AlertableError.swift */; };
C44CCD4127AFE2FC00CE40E5 /* AlertableError.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44CCD3F27AFE2FC00CE40E5 /* AlertableError.swift */; };
C44CCD4927AFF3B700CE40E5 /* MainMenu+Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44CCD4827AFF3B700CE40E5 /* MainMenu+Async.swift */; };
C44CCD4A27AFF3BC00CE40E5 /* MainMenu+Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44CCD4827AFF3B700CE40E5 /* MainMenu+Async.swift */; };
C464ADAC275A7A3F003FCD53 /* SiteListWC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C464ADAB275A7A3F003FCD53 /* SiteListWC.swift */; };
C464ADAD275A7A3F003FCD53 /* SiteListWC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C464ADAB275A7A3F003FCD53 /* SiteListWC.swift */; };
C464ADAF275A7A69003FCD53 /* SiteListVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C464ADAE275A7A69003FCD53 /* SiteListVC.swift */; };
@ -101,8 +97,9 @@
C48D0CA325CC992000CC7490 /* StatsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48D0CA225CC992000CC7490 /* StatsView.swift */; };
C48D6C70279CD2AC00F26D7E /* PhpVersionNumber.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48D6C6F279CD2AC00F26D7E /* PhpVersionNumber.swift */; };
C48D6C71279CD2AC00F26D7E /* PhpVersionNumber.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48D6C6F279CD2AC00F26D7E /* PhpVersionNumber.swift */; };
C48D6C72279CD2AC00F26D7E /* PhpVersionNumber.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48D6C6F279CD2AC00F26D7E /* PhpVersionNumber.swift */; };
C48D6C75279CD3E400F26D7E /* PhpVersionNumberTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48D6C73279CD3E400F26D7E /* PhpVersionNumberTest.swift */; };
C4927F0B27B2DFC200C55AFD /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4927F0A27B2DFC200C55AFD /* Errors.swift */; };
C4927F0C27B2DFC200C55AFD /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4927F0A27B2DFC200C55AFD /* Errors.swift */; };
C493084A279F331F009C240B /* AddSiteVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4930849279F331F009C240B /* AddSiteVC.swift */; };
C493084B279F331F009C240B /* AddSiteVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4930849279F331F009C240B /* AddSiteVC.swift */; };
C4998F0626175E7200B2526E /* HotKey in Frameworks */ = {isa = PBXBuildFile; productRef = C4998F0526175E7200B2526E /* HotKey */; };
@ -121,13 +118,10 @@
C4B56362276AB0A500F12CCB /* VersionExtractorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B56360276AB0A500F12CCB /* VersionExtractorTest.swift */; };
C4B5853E2770FE3900DA4FBE /* Paths.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853B2770FE3900DA4FBE /* Paths.swift */; };
C4B5853F2770FE3900DA4FBE /* Paths.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853B2770FE3900DA4FBE /* Paths.swift */; };
C4B585402770FE3900DA4FBE /* Paths.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853B2770FE3900DA4FBE /* Paths.swift */; };
C4B585412770FE3900DA4FBE /* Shell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853C2770FE3900DA4FBE /* Shell.swift */; };
C4B585422770FE3900DA4FBE /* Shell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853C2770FE3900DA4FBE /* Shell.swift */; };
C4B585432770FE3900DA4FBE /* Shell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853C2770FE3900DA4FBE /* Shell.swift */; };
C4B585442770FE3900DA4FBE /* Command.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853D2770FE3900DA4FBE /* Command.swift */; };
C4B585452770FE3900DA4FBE /* Command.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853D2770FE3900DA4FBE /* Command.swift */; };
C4B585462770FE3900DA4FBE /* Command.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853D2770FE3900DA4FBE /* Command.swift */; };
C4B97B75275CF08C003F3378 /* AppDelegate+MenuOutlets.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B97B74275CF08C003F3378 /* AppDelegate+MenuOutlets.swift */; };
C4B97B76275CF08C003F3378 /* AppDelegate+MenuOutlets.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B97B74275CF08C003F3378 /* AppDelegate+MenuOutlets.swift */; };
C4B97B78275CF1B5003F3378 /* App+ActivationPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B97B77275CF1B5003F3378 /* App+ActivationPolicy.swift */; };
@ -142,19 +136,20 @@
C4C8E81C276F54E5003AC782 /* PhpConfigWatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8E81A276F54E5003AC782 /* PhpConfigWatcher.swift */; };
C4CCBA6C275C567B008C7055 /* PMWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CCBA6B275C567B008C7055 /* PMWindowController.swift */; };
C4CCBA6D275C567B008C7055 /* PMWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CCBA6B275C567B008C7055 /* PMWindowController.swift */; };
C4CE3BB827B31F2E0086CA49 /* MainMenu+Switcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CE3BB727B31F2E0086CA49 /* MainMenu+Switcher.swift */; };
C4CE3BBA27B31F670086CA49 /* MainMenu+Composer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CE3BB927B31F670086CA49 /* MainMenu+Composer.swift */; };
C4CE3BBB27B324230086CA49 /* MainMenu+Switcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CE3BB727B31F2E0086CA49 /* MainMenu+Switcher.swift */; };
C4CE3BBC27B324250086CA49 /* MainMenu+Composer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CE3BB927B31F670086CA49 /* MainMenu+Composer.swift */; };
C4D8016622B1584700C6DA1B /* Startup.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D8016522B1584700C6DA1B /* Startup.swift */; };
C4D89BC62783C99400A02B68 /* ComposerJson.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D89BC52783C99400A02B68 /* ComposerJson.swift */; };
C4D9ADBF277610E1007277F4 /* PhpSwitcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D9ADBE277610E1007277F4 /* PhpSwitcher.swift */; };
C4D9ADC0277610E1007277F4 /* PhpSwitcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D9ADBE277610E1007277F4 /* PhpSwitcher.swift */; };
C4D9ADC1277610E1007277F4 /* PhpSwitcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D9ADBE277610E1007277F4 /* PhpSwitcher.swift */; };
C4D9ADC8277611A0007277F4 /* InternalSwitcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D9ADC7277611A0007277F4 /* InternalSwitcher.swift */; };
C4D9ADC9277611A0007277F4 /* InternalSwitcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D9ADC7277611A0007277F4 /* InternalSwitcher.swift */; };
C4D9ADCA277611A0007277F4 /* InternalSwitcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D9ADC7277611A0007277F4 /* InternalSwitcher.swift */; };
C4DEB7D427A5D60B00834718 /* Stats.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4DEB7D327A5D60B00834718 /* Stats.swift */; };
C4EC1E66279DE0380010F296 /* ServicesView.xib in Resources */ = {isa = PBXBuildFile; fileRef = C4EC1E65279DE0380010F296 /* ServicesView.xib */; };
C4EC1E68279DE0540010F296 /* ServicesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EC1E67279DE0540010F296 /* ServicesView.swift */; };
C4EC1E6D279DF87A0010F296 /* Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EC1E6C279DF87A0010F296 /* Async.swift */; };
C4EC1E6E279DF87A0010F296 /* Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EC1E6C279DF87A0010F296 /* Async.swift */; };
C4EC1E73279DFCF40010F296 /* Events.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EC1E72279DFCF40010F296 /* Events.swift */; };
C4EE188422D3386B00E126E5 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EE188322D3386B00E126E5 /* Constants.swift */; };
C4EE55A927708B9E001DF387 /* PMHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EE55A627708B9E001DF387 /* PMHeaderView.swift */; };
@ -170,12 +165,12 @@
C4F2E43B27530F750020E974 /* PhpInstallation.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F2E4392752F7D00020E974 /* PhpInstallation.swift */; };
C4F30B03278E16BA00755FCE /* HomebrewService.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F30B02278E16BA00755FCE /* HomebrewService.swift */; };
C4F30B04278E16BA00755FCE /* HomebrewService.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F30B02278E16BA00755FCE /* HomebrewService.swift */; };
C4F30B05278E16BA00755FCE /* HomebrewService.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F30B02278E16BA00755FCE /* HomebrewService.swift */; };
C4F30B07278E195800755FCE /* brew-services.json in Resources */ = {isa = PBXBuildFile; fileRef = C4F30B06278E195800755FCE /* brew-services.json */; };
C4F30B08278E195800755FCE /* brew-services.json in Resources */ = {isa = PBXBuildFile; fileRef = C4F30B06278E195800755FCE /* brew-services.json */; };
C4F30B09278E1A0E00755FCE /* CustomPrefs.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C3ED4227834C5200AB15D8 /* CustomPrefs.swift */; };
C4F30B0A278E1A1A00755FCE /* ComposerJson.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D89BC52783C99400A02B68 /* ComposerJson.swift */; };
C4F30B0B278E203C00755FCE /* MainMenu+Startup.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C3ED402783497000AB15D8 /* MainMenu+Startup.swift */; };
C4F319C927B034A500AFF46F /* Stats.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4DEB7D327A5D60B00834718 /* Stats.swift */; };
C4F7809C25D80344000DBC97 /* CommandTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F7809B25D80344000DBC97 /* CommandTest.swift */; };
C4F780A825D80AE8000DBC97 /* php.ini in Resources */ = {isa = PBXBuildFile; fileRef = C4F780A725D80AE8000DBC97 /* php.ini */; };
C4F780AE25D80B37000DBC97 /* ExtensionParserTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F780AD25D80B37000DBC97 /* ExtensionParserTest.swift */; };
@ -209,39 +204,27 @@
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
C415D3D42770F341005EF286 /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = /usr/share/man/man1/;
dstSubfolderSpec = 0;
files = (
);
runOnlyForDeploymentPostprocessing = 1;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
5420395826135DC100FB00FA /* PrefsVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrefsVC.swift; sourceTree = "<group>"; };
5420395E2613607600FB00FA /* Preferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = "<group>"; };
54AB03252763858F00A29D5F /* Timer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Timer.swift; sourceTree = "<group>"; };
54B48B5E275F66AE006D90C5 /* Application.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Application.swift; sourceTree = "<group>"; };
54FCFD25276C883F004CE748 /* CheckboxPreferenceView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = CheckboxPreferenceView.xib; sourceTree = "<group>"; };
54FCFD25276C883F004CE748 /* SelectPreferenceView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SelectPreferenceView.xib; sourceTree = "<group>"; };
54FCFD29276C8AA4004CE748 /* CheckboxPreferenceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxPreferenceView.swift; sourceTree = "<group>"; };
54FCFD2C276C8D67004CE748 /* HotkeyPreferenceView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = HotkeyPreferenceView.xib; sourceTree = "<group>"; };
54FCFD2F276C8DA4004CE748 /* HotkeyPreferenceView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HotkeyPreferenceView.swift; sourceTree = "<group>"; };
C405A4CE24B9B9130062FAFA /* InternetAccessPolicy.strings */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; path = InternetAccessPolicy.strings; sourceTree = "<group>"; };
C405A4CF24B9B9140062FAFA /* InternetAccessPolicy.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = InternetAccessPolicy.plist; sourceTree = "<group>"; };
C4068CA327B0780A00544CD5 /* CheckboxPreferenceView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = CheckboxPreferenceView.xib; sourceTree = "<group>"; };
C4068CA627B07A1300544CD5 /* SelectPreferenceView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SelectPreferenceView.swift; sourceTree = "<group>"; };
C4068CA927B0890D00544CD5 /* MenuBarIcons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuBarIcons.swift; sourceTree = "<group>"; };
C40C7F1D2772136000DDDCDC /* PhpEnv.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpEnv.swift; sourceTree = "<group>"; };
C40C7F2727721FF600DDDCDC /* ActivePhpInstallation+Checks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ActivePhpInstallation+Checks.swift"; sourceTree = "<group>"; };
C40C7F2F27722E8D00DDDCDC /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = "<group>"; };
C412E5FB25700D5300A1FB67 /* HomebrewPackage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomebrewPackage.swift; sourceTree = "<group>"; };
C415937E27A1B54F00D2E1B7 /* PhpFrameworks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpFrameworks.swift; sourceTree = "<group>"; };
C415D3B62770F294005EF286 /* Actions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Actions.swift; sourceTree = "<group>"; };
C415D3D62770F341005EF286 /* phpmon-cli */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "phpmon-cli"; sourceTree = BUILT_PRODUCTS_DIR; };
C415D3DE2770F34D005EF286 /* AllowedArguments.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AllowedArguments.swift; sourceTree = "<group>"; };
C415D3E52770F540005EF286 /* main.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
C415D3E72770F692005EF286 /* AppDelegate+InterApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+InterApp.swift"; sourceTree = "<group>"; };
C4168F4427ADB4A3003B6C39 /* DEVELOPER.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = DEVELOPER.md; sourceTree = "<group>"; };
C417DC73277614690015E6EE /* Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Helpers.swift; sourceTree = "<group>"; };
C4188988275FE8CB001EF227 /* Filesystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Filesystem.swift; sourceTree = "<group>"; };
C41C1B3322B0097F00E7CF16 /* PHP Monitor.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "PHP Monitor.app"; sourceTree = BUILT_PRODUCTS_DIR; };
@ -263,6 +246,8 @@
C43A8A2325D9D20D00591B77 /* BrewJsonParserTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrewJsonParserTest.swift; sourceTree = "<group>"; };
C44C198C276E3A1C0072762D /* ProgressWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressWindow.swift; sourceTree = "<group>"; };
C44C1990276E44CB0072762D /* ProgressWindow.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = ProgressWindow.storyboard; sourceTree = "<group>"; };
C44CCD3F27AFE2FC00CE40E5 /* AlertableError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertableError.swift; sourceTree = "<group>"; };
C44CCD4827AFF3B700CE40E5 /* MainMenu+Async.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainMenu+Async.swift"; sourceTree = "<group>"; };
C464ADAB275A7A3F003FCD53 /* SiteListWC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteListWC.swift; sourceTree = "<group>"; };
C464ADAE275A7A69003FCD53 /* SiteListVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteListVC.swift; sourceTree = "<group>"; };
C464ADB1275A87CA003FCD53 /* SiteListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteListCell.swift; sourceTree = "<group>"; };
@ -280,6 +265,7 @@
C48D0CA225CC992000CC7490 /* StatsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsView.swift; sourceTree = "<group>"; };
C48D6C6F279CD2AC00F26D7E /* PhpVersionNumber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpVersionNumber.swift; sourceTree = "<group>"; };
C48D6C73279CD3E400F26D7E /* PhpVersionNumberTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhpVersionNumberTest.swift; sourceTree = "<group>"; };
C4927F0A27B2DFC200C55AFD /* Errors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Errors.swift; sourceTree = "<group>"; };
C4930849279F331F009C240B /* AddSiteVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddSiteVC.swift; sourceTree = "<group>"; };
C4998F092617633900B2526E /* PrefsWC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrefsWC.swift; sourceTree = "<group>"; };
C49E171E27A5736E00787921 /* PMServicesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PMServicesView.swift; sourceTree = "<group>"; };
@ -301,6 +287,8 @@
C4C8E817276F54D8003AC782 /* App+ConfigWatch.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "App+ConfigWatch.swift"; sourceTree = "<group>"; };
C4C8E81A276F54E5003AC782 /* PhpConfigWatcher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhpConfigWatcher.swift; sourceTree = "<group>"; };
C4CCBA6B275C567B008C7055 /* PMWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PMWindowController.swift; sourceTree = "<group>"; };
C4CE3BB727B31F2E0086CA49 /* MainMenu+Switcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainMenu+Switcher.swift"; sourceTree = "<group>"; };
C4CE3BB927B31F670086CA49 /* MainMenu+Composer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainMenu+Composer.swift"; sourceTree = "<group>"; };
C4D8016522B1584700C6DA1B /* Startup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Startup.swift; sourceTree = "<group>"; };
C4D89BC52783C99400A02B68 /* ComposerJson.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposerJson.swift; sourceTree = "<group>"; };
C4D9ADBE277610E1007277F4 /* PhpSwitcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpSwitcher.swift; sourceTree = "<group>"; };
@ -333,13 +321,6 @@
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
C415D3D32770F341005EF286 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
C41C1B3022B0097F00E7CF16 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
@ -365,6 +346,7 @@
5420395826135DC100FB00FA /* PrefsVC.swift */,
5420395E2613607600FB00FA /* Preferences.swift */,
C4C3ED4227834C5200AB15D8 /* CustomPrefs.swift */,
C4068CA927B0890D00544CD5 /* MenuBarIcons.swift */,
C4DEB7D327A5D60B00834718 /* Stats.swift */,
C41CD0272628D8E20065BBED /* Keybinds */,
54FCFD28276C88C0004CE748 /* Views */,
@ -388,8 +370,10 @@
54FCFD28276C88C0004CE748 /* Views */ = {
isa = PBXGroup;
children = (
54FCFD25276C883F004CE748 /* CheckboxPreferenceView.xib */,
C4068CA327B0780A00544CD5 /* CheckboxPreferenceView.xib */,
54FCFD29276C8AA4004CE748 /* CheckboxPreferenceView.swift */,
54FCFD25276C883F004CE748 /* SelectPreferenceView.xib */,
C4068CA627B07A1300544CD5 /* SelectPreferenceView.swift */,
54FCFD2C276C8D67004CE748 /* HotkeyPreferenceView.xib */,
54FCFD2F276C8DA4004CE748 /* HotkeyPreferenceView.swift */,
);
@ -431,26 +415,16 @@
path = Core;
sourceTree = "<group>";
};
C415D3D72770F341005EF286 /* phpmon-cli */ = {
isa = PBXGroup;
children = (
C415D3E52770F540005EF286 /* main.swift */,
C415D3DE2770F34D005EF286 /* AllowedArguments.swift */,
);
path = "phpmon-cli";
sourceTree = "<group>";
};
C41C1B2A22B0097F00E7CF16 = {
isa = PBXGroup;
children = (
C4F8C0A522D4FA41002EFE61 /* README.md */,
C4E713562570150F00007428 /* SECURITY.md */,
C4F7807425D7F7E5000DBC97 /* RELEASE.md */,
C4168F4427ADB4A3003B6C39 /* DEVELOPER.md */,
C4E713572570151400007428 /* docs */,
C41C1B3522B0097F00E7CF16 /* phpmon */,
C4F7807A25D7F84B000DBC97 /* phpmon-tests */,
C415D3D72770F341005EF286 /* phpmon-cli */,
C4B5853A2770FE2500DA4FBE /* phpmon-common */,
C41C1B3422B0097F00E7CF16 /* Products */,
C4D309E72770EF2F00958BCF /* Frameworks */,
);
@ -461,7 +435,6 @@
children = (
C41C1B3322B0097F00E7CF16 /* PHP Monitor.app */,
C4F7807925D7F84B000DBC97 /* phpmon-tests.xctest */,
C415D3D62770F341005EF286 /* phpmon-cli */,
);
name = Products;
sourceTree = "<group>";
@ -469,6 +442,7 @@
C41C1B3522B0097F00E7CF16 /* phpmon */ = {
isa = PBXGroup;
children = (
C4B5853A2770FE2500DA4FBE /* Common */,
C41E181722CB61EB0072CF09 /* Domain */,
C41C1B3F22B0098000E7CF16 /* Info.plist */,
C4232EE42612526500158FC6 /* Credits.html */,
@ -492,15 +466,13 @@
isa = PBXGroup;
children = (
C4AF9F6B275445D300D44ED0 /* Integrations */,
C4B13B1D25C4915000548C3A /* Core */,
C4B13B1D25C4915000548C3A /* App */,
C4D9ADBD27761084007277F4 /* PHP */,
C47331A0247093AC009A0597 /* Menu */,
C464ADAA275A7A25003FCD53 /* SiteList */,
5420395726135DB800FB00FA /* Preferences */,
C44C198F276E3A380072762D /* Progress */,
C4C8E81D276F5686003AC782 /* Watcher */,
C4811D2822D70D9C00B5F6B3 /* Helpers */,
C4F8C0A222D4F100002EFE61 /* Extensions */,
C4EE55B027708BB2001DF387 /* SwiftUI */,
);
path = Domain;
@ -515,6 +487,15 @@
path = Progress;
sourceTree = "<group>";
};
C44CCD4327AFE93300CE40E5 /* Errors */ = {
isa = PBXGroup;
children = (
C44CCD3F27AFE2FC00CE40E5 /* AlertableError.swift */,
C4927F0A27B2DFC200C55AFD /* Errors.swift */,
);
path = Errors;
sourceTree = "<group>";
};
C464ADAA275A7A25003FCD53 /* SiteList */ = {
isa = PBXGroup;
children = (
@ -532,7 +513,10 @@
isa = PBXGroup;
children = (
C4811D2922D70F9A00B5F6B3 /* MainMenu.swift */,
C44CCD4827AFF3B700CE40E5 /* MainMenu+Async.swift */,
C4C3ED402783497000AB15D8 /* MainMenu+Startup.swift */,
C4CE3BB727B31F2E0086CA49 /* MainMenu+Switcher.swift */,
C4CE3BB927B31F670086CA49 /* MainMenu+Composer.swift */,
C47331A1247093B7009A0597 /* StatusMenu.swift */,
C48D0C9525CC80B100CC7490 /* HeaderView.swift */,
C48D0C9925CC888B00CC7490 /* HeaderView.xib */,
@ -553,7 +537,6 @@
C474B00524C0E98C00066A22 /* LocalNotification.swift */,
C41C1B4822B00A9800E7CF16 /* MenuBarImageGenerator.swift */,
C4CCBA6B275C567B008C7055 /* PMWindowController.swift */,
54AB03252763858F00A29D5F /* Timer.swift */,
C4B5635D276AB09000F12CCB /* VersionExtractor.swift */,
C4EC1E6C279DF87A0010F296 /* Async.swift */,
);
@ -595,7 +578,7 @@
path = Homebrew;
sourceTree = "<group>";
};
C4B13B1D25C4915000548C3A /* Core */ = {
C4B13B1D25C4915000548C3A /* App */ = {
isa = PBXGroup;
children = (
C41C1B3C22B0098000E7CF16 /* Main.storyboard */,
@ -609,16 +592,19 @@
C4D8016522B1584700C6DA1B /* Startup.swift */,
C4EED88827A48778006D7272 /* InterAppHandler.swift */,
);
path = Core;
path = App;
sourceTree = "<group>";
};
C4B5853A2770FE2500DA4FBE /* phpmon-common */ = {
C4B5853A2770FE2500DA4FBE /* Common */ = {
isa = PBXGroup;
children = (
C40C7F2127721F7300DDDCDC /* Core */,
54B20EDF263AA22C00D3250E /* PHP */,
C44CCD4327AFE93300CE40E5 /* Errors */,
C4F8C0A222D4F100002EFE61 /* Extensions */,
C4811D2822D70D9C00B5F6B3 /* Helpers */,
);
path = "phpmon-common";
path = Common;
sourceTree = "<group>";
};
C4C8E81D276F5686003AC782 /* Watcher */ = {
@ -715,23 +701,6 @@
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
C415D3D52770F341005EF286 /* phpmon-cli */ = {
isa = PBXNativeTarget;
buildConfigurationList = C415D3DA2770F341005EF286 /* Build configuration list for PBXNativeTarget "phpmon-cli" */;
buildPhases = (
C415D3D22770F341005EF286 /* Sources */,
C415D3D32770F341005EF286 /* Frameworks */,
C415D3D42770F341005EF286 /* CopyFiles */,
);
buildRules = (
);
dependencies = (
);
name = "phpmon-cli";
productName = "phpmon-cli";
productReference = C415D3D62770F341005EF286 /* phpmon-cli */;
productType = "com.apple.product-type.tool";
};
C41C1B3222B0097F00E7CF16 /* PHP Monitor */ = {
isa = PBXNativeTarget;
buildConfigurationList = C41C1B4322B0098000E7CF16 /* Build configuration list for PBXNativeTarget "PHP Monitor" */;
@ -780,9 +749,6 @@
LastUpgradeCheck = 1320;
ORGANIZATIONNAME = "Nico Verbruggen";
TargetAttributes = {
C415D3D52770F341005EF286 = {
CreatedOnToolsVersion = 13.2.1;
};
C41C1B3222B0097F00E7CF16 = {
CreatedOnToolsVersion = 10.2.1;
};
@ -810,7 +776,6 @@
targets = (
C41C1B3222B0097F00E7CF16 /* PHP Monitor */,
C4F7807825D7F84B000DBC97 /* phpmon-tests */,
C415D3D52770F341005EF286 /* phpmon-cli */,
);
};
/* End PBXProject section */
@ -827,9 +792,10 @@
C405A4D124B9B9140062FAFA /* InternetAccessPolicy.plist in Resources */,
C44C1991276E44CB0072762D /* ProgressWindow.storyboard in Resources */,
C4232EE52612526500158FC6 /* Credits.html in Resources */,
54FCFD26276C883F004CE748 /* CheckboxPreferenceView.xib in Resources */,
54FCFD26276C883F004CE748 /* SelectPreferenceView.xib in Resources */,
C473319F2470923A009A0597 /* Localizable.strings in Resources */,
C4F30B07278E195800755FCE /* brew-services.json in Resources */,
C4068CA427B0780A00544CD5 /* CheckboxPreferenceView.xib in Resources */,
C4EC1E66279DE0380010F296 /* ServicesView.xib in Resources */,
54FCFD2D276C8D67004CE748 /* HotkeyPreferenceView.xib in Resources */,
C405A4D024B9B9140062FAFA /* InternetAccessPolicy.strings in Resources */,
@ -841,9 +807,10 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
54FCFD27276C883F004CE748 /* CheckboxPreferenceView.xib in Resources */,
54FCFD27276C883F004CE748 /* SelectPreferenceView.xib in Resources */,
54FCFD2E276C8D67004CE748 /* HotkeyPreferenceView.xib in Resources */,
C4F780A825D80AE8000DBC97 /* php.ini in Resources */,
C4068CA527B0780A00544CD5 /* CheckboxPreferenceView.xib in Resources */,
C43A8A2025D9D1D700591B77 /* brew.json in Resources */,
C4AF9F72275445FF00D44ED0 /* valet-config.json in Resources */,
C44C1992276E44CB0072762D /* ProgressWindow.storyboard in Resources */,
@ -854,33 +821,6 @@
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
C415D3D22770F341005EF286 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
C40C7F2427721F8200DDDCDC /* PhpExtension.swift in Sources */,
C40C7F2627721FA200DDDCDC /* Constants.swift in Sources */,
C4B585402770FE3900DA4FBE /* Paths.swift in Sources */,
C415D3E62770F540005EF286 /* main.swift in Sources */,
C40B24F327A310780018C7D2 /* Events.swift in Sources */,
C40C7F2227721F8200DDDCDC /* PhpInstallation.swift in Sources */,
C4B585432770FE3900DA4FBE /* Shell.swift in Sources */,
C4D9ADC1277610E1007277F4 /* PhpSwitcher.swift in Sources */,
C4F30B05278E16BA00755FCE /* HomebrewService.swift in Sources */,
C4EC1E6E279DF87A0010F296 /* Async.swift in Sources */,
C40C7F2327721F8200DDDCDC /* ActivePhpInstallation.swift in Sources */,
C4B585462770FE3900DA4FBE /* Command.swift in Sources */,
C4D9ADCA277611A0007277F4 /* InternalSwitcher.swift in Sources */,
C48D6C72279CD2AC00F26D7E /* PhpVersionNumber.swift in Sources */,
C40C7F2527721F9800DDDCDC /* HomebrewPackage.swift in Sources */,
C417DC76277614690015E6EE /* Helpers.swift in Sources */,
C40C7F3227722E8D00DDDCDC /* Logger.swift in Sources */,
C40C7F2B2772201C00DDDCDC /* Actions.swift in Sources */,
C415D3E12770F34D005EF286 /* AllowedArguments.swift in Sources */,
C40C7F202772136000DDDCDC /* PhpEnv.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
C41C1B2F22B0097F00E7CF16 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
@ -894,6 +834,7 @@
C4AF9F7A2754499000D44ED0 /* Valet.swift in Sources */,
5420395926135DC100FB00FA /* PrefsVC.swift in Sources */,
C43603A0275E67610028EFC6 /* AppDelegate+Notifications.swift in Sources */,
C4068CA727B07A1300544CD5 /* SelectPreferenceView.swift in Sources */,
C49E171F27A5736E00787921 /* PMServicesView.swift in Sources */,
C4EE55AD27708B9E001DF387 /* PMStatsView.swift in Sources */,
C4C8E818276F54D8003AC782 /* App+ConfigWatch.swift in Sources */,
@ -914,6 +855,7 @@
C4C3ED4327834C5200AB15D8 /* CustomPrefs.swift in Sources */,
54B48B5F275F66AE006D90C5 /* Application.swift in Sources */,
C4B97B78275CF1B5003F3378 /* App+ActivationPolicy.swift in Sources */,
C4CE3BB827B31F2E0086CA49 /* MainMenu+Switcher.swift in Sources */,
C415937F27A1B54F00D2E1B7 /* PhpFrameworks.swift in Sources */,
C4811D2422D70A4700B5F6B3 /* App.swift in Sources */,
C41C1B4922B00A9800E7CF16 /* MenuBarImageGenerator.swift in Sources */,
@ -926,7 +868,7 @@
C41CA5ED2774F8EE00A2C80E /* SiteListVC+Actions.swift in Sources */,
C412E5FC25700D5300A1FB67 /* HomebrewPackage.swift in Sources */,
C4D9ADBF277610E1007277F4 /* PhpSwitcher.swift in Sources */,
54AB03262763858F00A29D5F /* Timer.swift in Sources */,
C4068CAA27B0890D00544CD5 /* MenuBarIcons.swift in Sources */,
C4C8E81B276F54E5003AC782 /* PhpConfigWatcher.swift in Sources */,
C417DC74277614690015E6EE /* Helpers.swift in Sources */,
C415D3E82770F692005EF286 /* AppDelegate+InterApp.swift in Sources */,
@ -934,9 +876,12 @@
C42759672627662800093CAE /* NSMenuExtension.swift in Sources */,
C464ADAF275A7A69003FCD53 /* SiteListVC.swift in Sources */,
C4EC1E6D279DF87A0010F296 /* Async.swift in Sources */,
C44CCD4927AFF3B700CE40E5 /* MainMenu+Async.swift in Sources */,
C4EC1E73279DFCF40010F296 /* Events.swift in Sources */,
C4927F0B27B2DFC200C55AFD /* Errors.swift in Sources */,
C4B5853E2770FE3900DA4FBE /* Paths.swift in Sources */,
C41C1B4B22B019FF00E7CF16 /* ActivePhpInstallation.swift in Sources */,
C44CCD4027AFE2FC00CE40E5 /* AlertableError.swift in Sources */,
C4188989275FE8CB001EF227 /* Filesystem.swift in Sources */,
C4B97B7B275CF20A003F3378 /* App+GlobalHotkey.swift in Sources */,
C4EED88927A48778006D7272 /* InterAppHandler.swift in Sources */,
@ -944,6 +889,7 @@
C476FF9822B0DD830098105B /* Alert.swift in Sources */,
C474B00624C0E98C00066A22 /* LocalNotification.swift in Sources */,
C48D0C9625CC80B100CC7490 /* HeaderView.swift in Sources */,
C4CE3BBA27B31F670086CA49 /* MainMenu+Composer.swift in Sources */,
C4D9ADC8277611A0007277F4 /* InternalSwitcher.swift in Sources */,
C4B5635E276AB09000F12CCB /* VersionExtractor.swift in Sources */,
C47331A2247093B7009A0597 /* StatusMenu.swift in Sources */,
@ -967,7 +913,6 @@
C4EE55AE27708B9E001DF387 /* PMStatsView.swift in Sources */,
C41CA5EE2774F8EE00A2C80E /* SiteListVC+Actions.swift in Sources */,
C4F780C425D80B75000DBC97 /* MainMenu.swift in Sources */,
54AB03272763858F00A29D5F /* Timer.swift in Sources */,
54FCFD2B276C8AA4004CE748 /* CheckboxPreferenceView.swift in Sources */,
C415D3B82770F294005EF286 /* Actions.swift in Sources */,
54B48B60275F66AE006D90C5 /* Application.swift in Sources */,
@ -976,12 +921,14 @@
C4D9ADC0277610E1007277F4 /* PhpSwitcher.swift in Sources */,
C4F780CC25D80B75000DBC97 /* ActivePhpInstallation.swift in Sources */,
C4F780B125D80B4D000DBC97 /* PhpExtension.swift in Sources */,
C4068CA827B07A1300544CD5 /* SelectPreferenceView.swift in Sources */,
C4F780CE25D80B75000DBC97 /* LocalNotification.swift in Sources */,
C40C7F2927721FF600DDDCDC /* ActivePhpInstallation+Checks.swift in Sources */,
C4FBFC532616485F00CDB8E1 /* PhpVersionDetectionTest.swift in Sources */,
C43A8A2425D9D20D00591B77 /* BrewJsonParserTest.swift in Sources */,
C4F780CA25D80B75000DBC97 /* HomebrewPackage.swift in Sources */,
C4C8E81C276F54E5003AC782 /* PhpConfigWatcher.swift in Sources */,
C4F319C927B034A500AFF46F /* Stats.swift in Sources */,
C4F30B04278E16BA00755FCE /* HomebrewService.swift in Sources */,
C4AF9F7B2754499000D44ED0 /* Valet.swift in Sources */,
C4F780C025D80B6E000DBC97 /* Startup.swift in Sources */,
@ -1000,15 +947,19 @@
C481F79726164A78004FBCFF /* PrefsVC.swift in Sources */,
C41E871B2763D42300161EE0 /* SiteListVC+ContextMenu.swift in Sources */,
C40C7F3127722E8D00DDDCDC /* Logger.swift in Sources */,
C4068CAB27B0890D00544CD5 /* MenuBarIcons.swift in Sources */,
C4F30B09278E1A0E00755FCE /* CustomPrefs.swift in Sources */,
C464ADB3275A87CA003FCD53 /* SiteListCell.swift in Sources */,
C415D3E92770F692005EF286 /* AppDelegate+InterApp.swift in Sources */,
C4AF9F78275447F100D44ED0 /* ValetConfigParserTest.swift in Sources */,
C4CE3BBC27B324250086CA49 /* MainMenu+Composer.swift in Sources */,
C40B24F427A310830018C7D2 /* StatusMenu.swift in Sources */,
C417DC75277614690015E6EE /* Helpers.swift in Sources */,
C4B97B7C275CF20A003F3378 /* App+GlobalHotkey.swift in Sources */,
C4B97B79275CF1B5003F3378 /* App+ActivationPolicy.swift in Sources */,
C4CE3BBB27B324230086CA49 /* MainMenu+Switcher.swift in Sources */,
C4F7809C25D80344000DBC97 /* CommandTest.swift in Sources */,
C44CCD4127AFE2FC00CE40E5 /* AlertableError.swift in Sources */,
C4F780BA25D80B62000DBC97 /* AppDelegate.swift in Sources */,
54FCFD31276C8DA4004CE748 /* HotkeyPreferenceView.swift in Sources */,
C4998F0B2617633900B2526E /* PrefsWC.swift in Sources */,
@ -1027,6 +978,8 @@
C40B24F127A3106D0018C7D2 /* ServicesView.swift in Sources */,
C4F780C525D80B75000DBC97 /* MenuBarImageGenerator.swift in Sources */,
C4F780B725D80B5D000DBC97 /* App.swift in Sources */,
C4927F0C27B2DFC200C55AFD /* Errors.swift in Sources */,
C44CCD4A27AFF3BC00CE40E5 /* MainMenu+Async.swift in Sources */,
C48D6C71279CD2AC00F26D7E /* PhpVersionNumber.swift in Sources */,
C4F780C925D80B75000DBC97 /* StringExtension.swift in Sources */,
C4B5853F2770FE3900DA4FBE /* Paths.swift in Sources */,
@ -1065,34 +1018,6 @@
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
C415D3DB2770F341005EF286 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
CODE_SIGN_IDENTITY = "-";
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = 8M54J5J787;
ENABLE_HARDENED_RUNTIME = YES;
MACOSX_DEPLOYMENT_TARGET = 11.0;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
};
name = Debug;
};
C415D3DC2770F341005EF286 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
CODE_SIGN_IDENTITY = "-";
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = 8M54J5J787;
ENABLE_HARDENED_RUNTIME = YES;
MACOSX_DEPLOYMENT_TARGET = 11.0;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
};
name = Release;
};
C41C1B4122B0098000E7CF16 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@ -1218,7 +1143,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 560;
CURRENT_PROJECT_VERSION = 610;
DEVELOPMENT_TEAM = 8M54J5J787;
ENABLE_HARDENED_RUNTIME = YES;
INFOPLIST_FILE = phpmon/Info.plist;
@ -1227,7 +1152,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 11.0;
MARKETING_VERSION = 5.0;
MARKETING_VERSION = 5.0.1;
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@ -1243,7 +1168,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 560;
CURRENT_PROJECT_VERSION = 610;
DEVELOPMENT_TEAM = 8M54J5J787;
ENABLE_HARDENED_RUNTIME = YES;
INFOPLIST_FILE = phpmon/Info.plist;
@ -1252,7 +1177,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 11.0;
MARKETING_VERSION = 5.0;
MARKETING_VERSION = 5.0.1;
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@ -1305,15 +1230,6 @@
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
C415D3DA2770F341005EF286 /* Build configuration list for PBXNativeTarget "phpmon-cli" */ = {
isa = XCConfigurationList;
buildConfigurations = (
C415D3DB2770F341005EF286 /* Debug */,
C415D3DC2770F341005EF286 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
C41C1B2E22B0097F00E7CF16 /* Build configuration list for PBXProject "PHP Monitor" */ = {
isa = XCConfigurationList;
buildConfigurations = (

View File

@ -1,84 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1320"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C415D3D52770F341005EF286"
BuildableName = "phpmon-cli"
BlueprintName = "phpmon-cli"
ReferencedContainer = "container:PHP Monitor.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C415D3D52770F341005EF286"
BuildableName = "phpmon-cli"
BlueprintName = "phpmon-cli"
ReferencedContainer = "container:PHP Monitor.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<CommandLineArguments>
<CommandLineArgument
argument = "help"
isEnabled = "YES">
</CommandLineArgument>
</CommandLineArguments>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C415D3D52770F341005EF286"
BuildableName = "phpmon-cli"
BlueprintName = "phpmon-cli"
ReferencedContainer = "container:PHP Monitor.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -320,9 +320,9 @@ You can always still ask Valet using the command line, should it be necessary. I
<details>
<summary><strong>After running PHP Monitor, Homebrew sometimes has issues with `brew upgrade` or `brew cleanup`!</strong></summary>
This is a security feature of Homebrew. 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 can now use **First Aid & Services > Restore Homebrew Permissions** to (temporarily) resolve this issue and allow for a clean and painless `brew upgrade` or `brew cleanup` process.
If you would like to know more, consult [this issue](https://github.com/nicoverbruggen/phpmon/issues/85) for more information.
If you would like to know more, consult [this issue](https://github.com/nicoverbruggen/phpmon/issues/85) for more information about why this is needed.
</details>
@ -399,15 +399,4 @@ I have done my best to annotate as much as humanly possible, and have avoided us
I also have a few tests for key parts of the application that I found needed to be tested. In the future, I would like to add even more tests for some of the UI stuff, but for now the tests are more unit tests than feature tests.
## 🔧 Build instructions
<img src="./docs/build.png" width="404px" alt="build button in Xcode"/>
If you'd like to build PHP Monitor yourself, you need:
* Xcode (usually the latest version)
* The contents of this repository
Once you have downloaded this repository, open `PHP Monitor.xcodeproj`, and you should be able to immediately build the app for your system by pressing Cmd-R. This will create a debug build. (If Xcode complains about code signing, you can turn it off.)
If you'd like to create a production build, choose "Any Mac" as the target and select Product > Archive.
For more detailed information for developers, please see [the documentation file for developers](./DEVS.md).

BIN
assets/icons.afdesign Normal file

Binary file not shown.

View File

@ -1,26 +0,0 @@
//
// AllowedArguments.swift
// phpmon-cli
//
// Created by Nico Verbruggen on 20/12/2021.
// Copyright © 2021 Nico Verbruggen. All rights reserved.
//
import Foundation
enum AllowedArguments: String, CaseIterable {
case use = "use"
case performSwitch = "switch"
case fix = "fix"
case help = "help"
static func has(_ string: String) -> Bool {
return Self.allCases.contains { arg in
return arg.rawValue == string
}
}
static var rawValues: [String] {
return Self.allCases.map { $0.rawValue }
}
}

View File

@ -1,103 +0,0 @@
//
// main.swift
// phpmon-cli
//
// Created by Nico Verbruggen on 20/12/2021.
// Copyright © 2021 Nico Verbruggen. All rights reserved.
//
import Foundation
let toolver = "0.1 (early access)"
let log = Log.shared
log.verbosity = .info
if CommandLine.arguments.contains("-q") || CommandLine.arguments.contains("--quiet") {
Log.shared.verbosity = .warning
}
if CommandLine.arguments.contains("-p") || CommandLine.arguments.contains("--performance") {
Log.shared.verbosity = .performance
}
var argument = "help"
if CommandLine.arguments.count > 1 {
argument = CommandLine.arguments[1]
}
if !AllowedArguments.has(argument) {
Log.err("The supported arguments are: \(AllowedArguments.rawValues)")
exit(1)
}
let action = AllowedArguments.init(rawValue: argument)
switch action {
case .use, .performSwitch:
if !Shell.fileExists("\(Paths.binPath)/php") {
Log.err("PHP is currently not linked. Attempting quick fix...")
_ = Shell.user.executeSynchronously("brew link php", requiresPath: true)
}
let phpenv = PhpEnv.shared
PhpEnv.detectPhpVersions()
if CommandLine.arguments.count < 3 {
Log.err("You must enter at least two additional arguments when using this command.")
exit(1)
}
let version = CommandLine.arguments[2].replacingOccurrences(of: "php@", with: "")
if phpenv.availablePhpVersions.contains(version) {
Log.info("Switching to PHP \(version)...")
Actions.switchToPhpVersion(
version: version,
availableVersions: phpenv.availablePhpVersions,
completed: {
Log.info("The switch has been completed.")
exit(0)
}
)
} else {
Log.err("A PHP installation with version \(version) is not installed.")
Log.err("The installed versions are: \(phpenv.availablePhpVersions.joined(separator: ", ")).")
Log.err("If this version is available, you may be able to install it by using `brew install php@\(version)`.")
exit(1)
}
case .fix:
Log.info("Fixing your PHP installation...")
Actions.fixMyPhp()
Log.info("All operations completed. You can check which version of PHP is linked by using `php -v`.")
exit(0)
case .help:
print("""
===============================================================
PHP MONITOR CLI \(toolver)
by Nico Verbruggen
===============================================================
Gives access to the quick version switcher from PHP Monitor,
but without the GUI and 100% of the speed!
SUPPORTED COMMANDS
* use {version}: Switch to a specific version of PHP.
(e.g. `phpmon-cli use 8.0`)
* switch {version}: Alias for the `use` command.
* fix Attempts to unlink all PHP versions,
and link the latest version of PHP.
* help: Show this help.
SUPPORTED FLAGS
* `-q / --quiet`: Silences all logs except for warnings and exceptions.
* `-p / --perf`: Enables performance mode.
""")
exit(0)
case .none:
Log.err("Action not recognized!")
exit(1)
}
RunLoop.main.run()

View File

@ -11,6 +11,10 @@ import XCTest
class PhpVersionNumberTest: XCTestCase {
func testCanDeconstructPhpVersion() throws {
XCTAssertEqual(
try! PhpVersionNumber.parse("PHP 8.1.0RC5-dev"),
PhpVersionNumber(major: 8, minor: 1, patch: 0)
)
XCTAssertEqual(
PhpVersionNumber.make(from: "8.0.11"),
PhpVersionNumber(major: 8, minor: 0, patch: 11)
@ -29,6 +33,12 @@ class PhpVersionNumberTest: XCTestCase {
)
}
func testPhpVersionNumberParse() throws {
XCTAssertThrowsError(try PhpVersionNumber.parse("OOF")) { error in
XCTAssertTrue(error is VersionParseError)
}
}
func testCanCheckFixedConstraints() throws {
XCTAssertEqual(
PhpVersionNumberCollection

View File

@ -1,11 +1,12 @@
{
"images" : [
{
"filename" : "Linked.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "link.svg",
"filename" : "Linked@2x.png",
"idiom" : "universal",
"scale" : "2x"
},

Binary file not shown.

After

Width:  |  Height:  |  Size: 978 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M326.612 185.391c59.747 59.809 58.927 155.698.36 214.59-.11.12-.24.25-.36.37l-67.2 67.2c-59.27 59.27-155.699 59.262-214.96 0-59.27-59.26-59.27-155.7 0-214.96l37.106-37.106c9.84-9.84 26.786-3.3 27.294 10.606.648 17.722 3.826 35.527 9.69 52.721 1.986 5.822.567 12.262-3.783 16.612l-13.087 13.087c-28.026 28.026-28.905 73.66-1.155 101.96 28.024 28.579 74.086 28.749 102.325.51l67.2-67.19c28.191-28.191 28.073-73.757 0-101.83-3.701-3.694-7.429-6.564-10.341-8.569a16.037 16.037 0 0 1-6.947-12.606c-.396-10.567 3.348-21.456 11.698-29.806l21.054-21.055c5.521-5.521 14.182-6.199 20.584-1.731a152.482 152.482 0 0 1 20.522 17.197zM467.547 44.449c-59.261-59.262-155.69-59.27-214.96 0l-67.2 67.2c-.12.12-.25.25-.36.37-58.566 58.892-59.387 154.781.36 214.59a152.454 152.454 0 0 0 20.521 17.196c6.402 4.468 15.064 3.789 20.584-1.731l21.054-21.055c8.35-8.35 12.094-19.239 11.698-29.806a16.037 16.037 0 0 0-6.947-12.606c-2.912-2.005-6.64-4.875-10.341-8.569-28.073-28.073-28.191-73.639 0-101.83l67.2-67.19c28.239-28.239 74.3-28.069 102.325.51 27.75 28.3 26.872 73.934-1.155 101.96l-13.087 13.087c-4.35 4.35-5.769 10.79-3.783 16.612 5.864 17.194 9.042 34.999 9.69 52.721.509 13.906 17.454 20.446 27.294 10.606l37.106-37.106c59.271-59.259 59.271-155.699.001-214.959z"/></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -1,11 +1,12 @@
{
"images" : [
{
"filename" : "Parked.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "car-alt.svg",
"filename" : "Parked@2x.png",
"idiom" : "universal",
"scale" : "2x"
},

Binary file not shown.

After

Width:  |  Height:  |  Size: 868 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 480 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M438.66 212.33l-11.24-28.1-19.93-49.83C390.38 91.63 349.57 64 303.5 64h-127c-46.06 0-86.88 27.63-103.99 70.4l-19.93 49.83-11.24 28.1C17.22 221.5 0 244.66 0 272v48c0 16.12 6.16 30.67 16 41.93V416c0 17.67 14.33 32 32 32h32c17.67 0 32-14.33 32-32v-32h256v32c0 17.67 14.33 32 32 32h32c17.67 0 32-14.33 32-32v-54.07c9.84-11.25 16-25.8 16-41.93v-48c0-27.34-17.22-50.5-41.34-59.67zm-306.73-54.16c7.29-18.22 24.94-30.17 44.57-30.17h127c19.63 0 37.28 11.95 44.57 30.17L368 208H112l19.93-49.83zM80 319.8c-19.2 0-32-12.76-32-31.9S60.8 256 80 256s48 28.71 48 47.85-28.8 15.95-48 15.95zm320 0c-19.2 0-48 3.19-48-15.95S380.8 256 400 256s32 12.76 32 31.9-12.8 31.9-32 31.9z"/></svg>

Before

Width:  |  Height:  |  Size: 918 B

View File

@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,22 @@
{
"images" : [
{
"filename" : "Menu Bar Elephant.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "Menu Bar Elephant@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -1,11 +1,12 @@
{
"images" : [
{
"filename" : "Menu Bar.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "php@2x.png",
"filename" : "Menu Bar@2x.png",
"idiom" : "universal",
"scale" : "2x"
},

Binary file not shown.

After

Width:  |  Height:  |  Size: 658 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 793 B

View File

@ -1,11 +1,12 @@
{
"images" : [
{
"filename" : "ServiceLoading.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "question-circle.svg",
"filename" : "ServiceLoading@2x.png",
"idiom" : "universal",
"scale" : "2x"
},

Binary file not shown.

After

Width:  |  Height:  |  Size: 854 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!-- Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M256 8C119.043 8 8 119.083 8 256c0 136.997 111.043 248 248 248s248-111.003 248-248C504 119.083 392.957 8 256 8zm0 448c-110.532 0-200-89.431-200-200 0-110.495 89.472-200 200-200 110.491 0 200 89.471 200 200 0 110.53-89.431 200-200 200zm107.244-255.2c0 67.052-72.421 68.084-72.421 92.863V300c0 6.627-5.373 12-12 12h-45.647c-6.627 0-12-5.373-12-12v-8.659c0-35.745 27.1-50.034 47.579-61.516 17.561-9.845 28.324-16.541 28.324-29.579 0-17.246-21.999-28.693-39.784-28.693-23.189 0-33.894 10.977-48.942 29.969-4.057 5.12-11.46 6.071-16.666 2.124l-27.824-21.098c-5.107-3.872-6.251-11.066-2.644-16.363C184.846 131.491 214.94 112 261.794 112c49.071 0 101.45 38.304 101.45 88.8zM298 368c0 23.159-18.841 42-42 42s-42-18.841-42-42 18.841-42 42-42 42 18.841 42 42z"/></svg>

Before

Width:  |  Height:  |  Size: 966 B

View File

@ -1,11 +1,12 @@
{
"images" : [
{
"filename" : "ServiceOff.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "times-circle.svg",
"filename" : "ServiceOff@2x.png",
"idiom" : "universal",
"scale" : "2x"
},

Binary file not shown.

After

Width:  |  Height:  |  Size: 826 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!-- Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm0 448c-110.5 0-200-89.5-200-200S145.5 56 256 56s200 89.5 200 200-89.5 200-200 200zm101.8-262.2L295.6 256l62.2 62.2c4.7 4.7 4.7 12.3 0 17l-22.6 22.6c-4.7 4.7-12.3 4.7-17 0L256 295.6l-62.2 62.2c-4.7 4.7-12.3 4.7-17 0l-22.6-22.6c-4.7-4.7-4.7-12.3 0-17l62.2-62.2-62.2-62.2c-4.7-4.7-4.7-12.3 0-17l22.6-22.6c4.7-4.7 12.3-4.7 17 0l62.2 62.2 62.2-62.2c4.7-4.7 12.3-4.7 17 0l22.6 22.6c4.7 4.7 4.7 12.3 0 17z"/></svg>

Before

Width:  |  Height:  |  Size: 685 B

View File

@ -1,11 +1,12 @@
{
"images" : [
{
"filename" : "ServiceOn.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "check-circle.svg",
"filename" : "ServiceOn@2x.png",
"idiom" : "universal",
"scale" : "2x"
},

Binary file not shown.

After

Width:  |  Height:  |  Size: 819 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!-- Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M256 8C119.033 8 8 119.033 8 256s111.033 248 248 248 248-111.033 248-248S392.967 8 256 8zm0 48c110.532 0 200 89.451 200 200 0 110.532-89.451 200-200 200-110.532 0-200-89.451-200-200 0-110.532 89.451-200 200-200m140.204 130.267l-22.536-22.718c-4.667-4.705-12.265-4.736-16.97-.068L215.346 303.697l-59.792-60.277c-4.667-4.705-12.265-4.736-16.97-.069l-22.719 22.536c-4.705 4.667-4.736 12.265-.068 16.971l90.781 91.516c4.667 4.705 12.265 4.736 16.97.068l172.589-171.204c4.704-4.668 4.734-12.266.067-16.971z"/></svg>

Before

Width:  |  Height:  |  Size: 718 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 940 B

View File

@ -34,17 +34,39 @@ class Actions {
brew("services stop dnsmasq", sudo: true)
}
/**
Kindly asks Valet to switch to a specific PHP version.
*/
public static func switchToPhpVersionUsingValet(
version: String,
availableVersions: [String],
completed: @escaping () -> Void
) {
Log.info("Switching to \(version) using Valet")
Log.info(valet("use php@\(version)"))
completed()
public static func fixHomebrewPermissions() throws
{
var servicesCommands = [
"\(Paths.brew) services stop nginx",
"\(Paths.brew) services stop dnsmasq",
]
var cellarCommands = [
"chown -R \(Paths.whoami):staff \(Paths.cellarPath)/nginx",
"chown -R \(Paths.whoami):staff \(Paths.cellarPath)/dnsmasq"
]
PhpEnv.shared.availablePhpVersions.forEach { version in
let formula = version == PhpEnv.brewPhpVersion
? "php"
: "php@\(version)"
servicesCommands.append("\(Paths.brew) services stop \(formula)")
cellarCommands.append("chown -R \(Paths.whoami):staff \(Paths.cellarPath)/\(formula)")
}
let script =
servicesCommands.joined(separator: " && ")
+ " && "
+ cellarCommands.joined(separator: " && ")
let appleScript = NSAppleScript(
source: "do shell script \"\(script)\" with administrator privileges"
)
let eventResult: NSAppleEventDescriptor? = appleScript?.executeAndReturnError(nil)
if (eventResult == nil) {
throw HomebrewPermissionError(kind: .applescriptNilError)
}
}
// MARK: - Finding Config Files

View File

@ -27,13 +27,13 @@ class Log {
static func err(_ item: Any) {
if Verbosity.error.isApplicable() {
print(item)
print("[ERR] \(item)")
}
}
static func warn(_ item: Any) {
if Verbosity.warning.isApplicable() {
print(item)
print("[WRN] \(item)")
}
}

View File

@ -20,7 +20,7 @@ public class Paths {
private var userName : String
init() {
baseDir = Shell.fileExists("\(HomebrewDir.opt.rawValue)/bin/brew") ? .opt : .usr
baseDir = FileManager.default.fileExists(atPath: "\(HomebrewDir.opt.rawValue)/bin/brew") ? .opt : .usr
userName = String(Shell.pipe("whoami").split(separator: "\n")[0])
}
@ -48,6 +48,10 @@ public class Paths {
return shared.userName
}
public static var cellarPath: String {
return "\(shared.baseDir.rawValue)/Cellar"
}
public static var binPath: String {
return "\(shared.baseDir.rawValue)/bin"
}

View File

@ -105,12 +105,12 @@ public class Shell {
}
/**
Checks if a file exists at the provided path.
Uses `/bin/echo` instead of the `builtin` (which does not support `-n`).
Checks if a file exists at a certain path.
Used to be done with a shell command, now uses the native FileManager class instead.
*/
public static func fileExists(_ path: String) -> Bool {
let escapedPath = path.replacingOccurrences(of: " ", with: "\\ ")
return Shell.pipe("if [ -f \(escapedPath) ]; then /bin/echo -n \"0\"; fi") == "0"
let fullPath = path.replacingOccurrences(of: "~", with: "/Users/\(Paths.whoami)")
return FileManager.default.fileExists(atPath: fullPath)
}
/**

View File

@ -0,0 +1,13 @@
//
// Errors.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 06/02/2022.
// Copyright © 2022 Nico Verbruggen. All rights reserved.
//
import Foundation
protocol AlertableError {
func getErrorMessageKey() -> String
}

View File

@ -0,0 +1,29 @@
//
// VersionParseError.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 08/02/2022.
// Copyright © 2022 Nico Verbruggen. All rights reserved.
//
import Foundation
// MARK: - Alertable Errors
// These errors must be resolved by the user.
struct HomebrewPermissionError: Error, AlertableError {
enum Kind: String {
case applescriptNilError = "homebrew_permissions.applescript_returned_nil"
}
let kind: Kind
func getErrorMessageKey() -> String {
return "alert.errors.\(self.kind.rawValue)"
}
}
// MARK: - Errors that do not have an associated alert message
// The errors must be resolved by the developer.
struct VersionParseError: Error {}

View File

@ -51,6 +51,9 @@ class Alert {
}
}
/**
Notify the user about something by showing an alert.
*/
public static func notify(message: String, info: String, style: NSAlert.Style = .informational) {
_ = present(
messageText: message,
@ -59,5 +62,20 @@ class Alert {
secondButtonTitle: "",
style: style
)
}
}
/**
Notify the user about a particular error (which must be `Alertable`)
by showing an alert.
*/
public static func notify(about error: Error & AlertableError) {
let key = error.getErrorMessageKey()
_ = present(
messageText: "\(key).title".localized,
informativeText: "\(key).description".localized,
buttonTitle: "OK",
secondButtonTitle: "",
style: .critical
)
}
}

View File

@ -42,7 +42,7 @@ class MenuBarImageGenerator {
let targetImage: NSImage = NSImage(size: image.size)
let rep: NSBitmapImageRep = NSBitmapImageRep(
let representation: NSBitmapImageRep = NSBitmapImageRep(
bitmapDataPlanes: nil,
pixelsWide: Int(image.size.width),
pixelsHigh: Int(image.size.height),
@ -55,7 +55,7 @@ class MenuBarImageGenerator {
bitsPerPixel: 0
)!
targetImage.addRepresentation(rep)
targetImage.addRepresentation(representation)
targetImage.lockFocus()
image.draw(in: imageRect)
@ -69,32 +69,69 @@ class MenuBarImageGenerator {
The same as before, but also attempts to add an icon to the left.
*/
public static func textToImageWithIcon(text: String) -> NSImage {
let textImage = self.textToImage(text: text)
let iconImage = NSImage(named: "StatusBarPHP")!
let iconWidthSize = iconImage.size.width
let divider = iconWidthSize
// We'll start out with the image containing the text
let textImage = self.textToImage(text: text)
// Then we'll fetch the image we want on the left
var iconType = Preferences.preferences[.iconTypeToDisplay] as? String
if iconType == nil {
Log.warn("Invalid icon type found, using the default")
iconType = MenuBarIcon.iconPhp.rawValue
}
let iconImage = NSImage(named: "MenuBar_\(iconType!)")!
// We'll need to reference the width of the icon a bunch of times
let iconWidthSize = iconImage.size.width
// There will also be an additional divider between the image and the text (image)
let divider: CGFloat = 3
// Use a fixed size for the height of the menu bar (18pt)
let imageRect = CGRect(
x: 0,
y: 0,
width: textImage.size.width + divider,
height: textImage.size.height
width: textImage.size.width + iconWidthSize + divider,
height: 18
)
// Create a new image, we'll draw the text and our icon in there
let image: NSImage = NSImage(size: imageRect.size)
image.lockFocus()
// Calculate the offset between the image and the text
let offset = imageRect.size.width - textImage.size.width
let difference = imageRect.size.width - textImage.size.width
// Draw the text with a negative x offset (so there is room on the left for the icon)
textImage.draw(
in: imageRect,
from: NSRect(
x: -offset,
y: 0,
width: textImage.size.width + offset,
height: textImage.size.height
),
operation: .overlay,
fraction: 1
)
textImage.draw(in: imageRect, from: NSRect(
x: -difference,
y: 0, width: textImage.size.width + difference,
height: textImage.size.height
), operation: .overlay, fraction: 1)
iconImage.draw(in: imageRect, from: NSRect(x: 0, y: 0, width: imageRect.size.width * 1.6, height: imageRect.size.height * 2.0), operation: .overlay, fraction: 1)
// Draw the icon directly in the left of the imageRect (where we left space)
iconImage.draw(
in: imageRect,
from: NSRect(
x: 0,
y: 0,
width: imageRect.size.width,
height: imageRect.size.height
),
operation: .overlay,
fraction: 1
)
// We're done with this image
image.unlockFocus()
return image
}

View File

@ -16,7 +16,7 @@ class VersionExtractor {
public static func from(_ string: String) -> String? {
do {
let regex = try NSRegularExpression(
pattern: #"Laravel Valet (?<version>(\d+)(.)(\d+)((.)(\d+))?)"#,
pattern: #"(?<version>(\d+)(.)(\d+)((.)(\d+))?)"#,
options: []
)

View File

@ -8,11 +8,6 @@
import Foundation
protocol PhpSwitcherDelegate: AnyObject {
func switcherDidStartSwitching()
func switcherDidCompleteSwitch()
}
class PhpEnv {
// MARK: - Initializer
@ -161,7 +156,7 @@ class PhpEnv {
*/
public func validate(_ version: String) -> Bool {
if self.currentInstall.version.short == version {
print("Switching to version \(version) seems to have succeeded. Validation passed.")
Log.info("Switching to version \(version) seems to have succeeded. Validation passed.")
return true
}

View File

@ -118,6 +118,14 @@ public struct PhpVersionNumber: Equatable {
*/
}
public static func parse(_ text: String) throws -> Self {
guard let versionText = VersionExtractor.from(text) else {
throw VersionParseError()
}
return Self.make(from: versionText)!
}
public static func make(from versionString: String, type: MatchType = .versionOnly) -> Self? {
let regex = try! NSRegularExpression(pattern: type.rawValue, options: [])
let match = regex.matches(in: versionString, options: [], range: NSMakeRange(0, versionString.count)).first

View File

@ -27,9 +27,10 @@ class PhpInstallation {
arguments: ["--version"]
).trimmingCharacters(in: .whitespacesAndNewlines)
self.longVersion = PhpVersionNumber.make(
from: String(longVersionString.split(separator: "-")[0])
)!
// The parser should always work, or the string has to be very unusual.
// If so, the app SHOULD crash, so that the users report what's up.
// TODO: Alert the user that the version number could not be parsed.
self.longVersion = try! PhpVersionNumber.parse(longVersionString)
}
}

View File

@ -8,6 +8,14 @@
import Foundation
protocol PhpSwitcherDelegate: AnyObject {
func switcherDidStartSwitching(to: String)
func switcherDidCompleteSwitch(to: String)
}
protocol PhpSwitcher {
func performSwitch(to version: String, completion: @escaping () -> Void)

View File

@ -8,7 +8,7 @@
import Cocoa
import HotKey
class App: PhpSwitcherDelegate {
class App {
// MARK: Static Vars
@ -73,20 +73,4 @@ class App: PhpSwitcherDelegate {
The `PhpConfigWatcher` is responsible for watching the `.ini` files and the `.conf.d` folder.
*/
var watcher: PhpConfigWatcher!
// MARK: - PhpSwitcherDelegate
func switcherDidStartSwitching() {
}
func switcherDidCompleteSwitch() {
PhpEnv.shared.currentInstall = ActivePhpInstallation()
handlePhpConfigWatcher()
if let window = siteListWindowController {
DispatchQueue.main.async {
window.contentVC.reloadSites()
}
}
}
}

View File

@ -45,12 +45,16 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele
let valet: Valet
/**
The PhpSwitcher singleton that handles PHP version
The PhpEnv singleton that handles PHP version
detection, as well as switching. It is initialized
when the app is ready and passed all checks.
*/
var switcher: PhpEnv! = nil
var phpEnvironment: PhpEnv! = nil
/**
The logger is responsible for different levels of logging.
You can tweak the verbosity in the `init` method here.
*/
var logger = Log.shared
// MARK: - Initializer
@ -59,7 +63,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele
When the application initializes, create all singletons.
*/
override init() {
logger.verbosity = .performance
logger.verbosity = .info
Log.info("==================================")
Log.info("PHP MONITOR by Nico Verbruggen")
Log.info("Version \(App.version)")
@ -73,8 +77,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele
}
func initializeSwitcher() {
self.switcher = PhpEnv.shared
self.switcher.delegate = self.state
self.phpEnvironment = PhpEnv.shared
}
// MARK: - Lifecycle

View File

@ -771,7 +771,7 @@ Gw
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="600" id="iRQ-sz-oyv"/>
</constraints>
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="TDE-ff-DQT">
<rect key="frame" x="1" y="293" width="598" height="15"/>
<rect key="frame" x="1" y="292" width="598" height="16"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<scroller key="verticalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="wFn-93-f10">
@ -808,7 +808,7 @@ Gw
</scenes>
<resources>
<image name="Checkmark" width="512" height="512"/>
<image name="IconLinked" width="512" height="512"/>
<image name="IconLinked" width="25" height="25"/>
<image name="Lock" width="30" height="30"/>
<image name="arrow.clockwise" catalog="system" width="14" height="16"/>
<image name="plus" catalog="system" width="14" height="13"/>

View File

@ -49,14 +49,6 @@ class Startup {
breaking: true
)
Valet.shared.version = VersionExtractor.from(valet("--version"))
performEnvironmentCheck(
Valet.shared.version == nil,
messageText: "startup.errors.valet_version_unknown.title".localized,
informativeText: "startup.errors.valet_version_unknown.desc".localized,
breaking: true
)
performEnvironmentCheck(
HomebrewDiagnostics.cannotLoadService(),
messageText: "startup.errors.services_json_error.title".localized,
@ -81,12 +73,13 @@ class Startup {
breaking: true
)
let services = Shell.pipe("\(Paths.brew) services list | grep php")
// Determine the Valet version only AFTER confirming the correct permission is in place
Valet.shared.version = VersionExtractor.from(valet("--version"))
performEnvironmentCheck(
(services.countInstances(of: "started") > 1),
messageText: "startup.errors.services.title".localized,
informativeText: "startup.errors.services.desc".localized,
breaking: false
Valet.shared.version == nil,
messageText: "startup.errors.valet_version_unknown.title".localized,
informativeText: "startup.errors.valet_version_unknown.desc".localized,
breaking: true
)
if (!failed) {

View File

@ -1,32 +0,0 @@
//
// BenchmarkTimer.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 10/12/2021.
// Copyright © 2021 Nico Verbruggen. All rights reserved.
//
import Foundation
class BenchmarkTimer {
let startTime: CFAbsoluteTime
var endTime: CFAbsoluteTime?
init() {
startTime = CFAbsoluteTimeGetCurrent()
}
func stop() -> CFAbsoluteTime {
endTime = CFAbsoluteTimeGetCurrent()
return duration!
}
var duration: CFAbsoluteTime? {
if let endTime = endTime {
return endTime - startTime
} else {
return nil
}
}
}

View File

@ -163,7 +163,7 @@ class Valet {
// We should also check that we can interpret the path correctly
if URL(fileURLWithPath: siteDir).lastPathComponent == "" {
print("Warning: could not parse the site: \(siteDir), skipping!")
Log.warn("Could not parse the site: \(siteDir), skipping!")
return
}

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="17701" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="19529" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="17701"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="19529"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@ -10,7 +10,7 @@
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<customView id="c22-O7-iKe" customClass="HeaderView" customModule="PHP_Monitor" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="350" height="24"/>
<rect key="frame" x="0.0" y="0.0" width="270" height="24"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ddg-VQ-cOT">
@ -29,7 +29,7 @@
<connections>
<outlet property="textField" destination="ddg-VQ-cOT" id="aaQ-Xb-o2X"/>
</connections>
<point key="canvasLocation" x="-75" y="38"/>
<point key="canvasLocation" x="177" y="105"/>
</customView>
</objects>
</document>

View File

@ -0,0 +1,96 @@
//
// MainMenu+Async.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 06/02/2022.
// Copyright © 2022 Nico Verbruggen. All rights reserved.
//
import Foundation
extension MainMenu {
// MARK: - Nicer callbacks
enum AsyncBehaviour {
case setsBusyUI
case reloadsPhpInstallation
case updatesMenuBarContents
case broadcastServicesUpdate
}
/**
Attempts asynchronous execution of a callback that may throw an `Error`.
While the callback is being executed, the UI will be marked as busy.
(Preferably, if an `Error` is thrown, it should also be an `AlertableError`,
which will make presenting errors easier.)
- Parameter execute: Required callback of the work that needs to happen.
- Parameter success: Optional callback that is fired when all was OK.
- Parameter failure: Optional callback that is fired when an `Error` was thrown.
- Parameter behaviours: Various behaviours that can be tweaked, but usually best left to the default.
The default will set the UI to busy, reload PHP info, update the menu bar,
and broadcast to the services view that the list has been updated.
*/
func asyncExecution(
_ execute: @escaping () throws -> Void,
success: @escaping () -> Void = {},
failure: @escaping (Error) -> Void = { _ in },
behaviours: [AsyncBehaviour] = [
.setsBusyUI,
.reloadsPhpInstallation,
.updatesMenuBarContents,
.broadcastServicesUpdate
]
) {
if behaviours.contains(.reloadsPhpInstallation) {
PhpEnv.shared.isBusy = true
}
if behaviours.contains(.setsBusyUI) {
setBusyImage()
}
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
var error: Error? = nil
do { try execute() } catch let e { error = e }
if behaviours.contains(.setsBusyUI) {
PhpEnv.shared.isBusy = false
}
DispatchQueue.main.async { [self] in
if behaviours.contains(.reloadsPhpInstallation) {
PhpEnv.shared.currentInstall = ActivePhpInstallation()
}
if behaviours.contains(.updatesMenuBarContents) {
updatePhpVersionInStatusBar()
} else if behaviours.contains(.setsBusyUI) {
refreshIcon()
}
if behaviours.contains(.broadcastServicesUpdate) {
NotificationCenter.default.post(name: Events.ServicesUpdated, object: nil)
}
error == nil ? success() : failure(error!)
}
}
}
func asyncWithBusyUI(
_ execute: @escaping () throws -> Void,
completion: @escaping () -> Void = {}
) {
asyncExecution({
try! execute()
}, success: {
completion()
}, behaviours: [.setsBusyUI])
}
}

View File

@ -0,0 +1,98 @@
//
// MainMenu+Composer.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 08/02/2022.
// Copyright © 2022 Nico Verbruggen. All rights reserved.
//
import Foundation
extension MainMenu {
/**
Updates the global dependencies and runs the completion callback when done.
This method should probably be broken up into several smaller methods at some point.
*/
func updateGlobalDependencies(notify: Bool, completion: @escaping (Bool) -> Void) {
if !Shell.fileExists("/usr/local/bin/composer") {
Alert.notify(
message: "alert.composer_missing.title".localized,
info: "alert.composer_missing.info".localized
)
return
}
PhpEnv.shared.isBusy = true
setBusyImage()
self.rebuild()
let noLongerBusy = {
PhpEnv.shared.isBusy = false
DispatchQueue.main.async { [self] in
self.updatePhpVersionInStatusBar()
self.rebuild()
}
}
var window: ProgressWindowController? = ProgressWindowController.display(
title: "alert.composer_progress.title".localized,
description: "alert.composer_progress.info".localized
)
window?.setType(info: true)
DispatchQueue.global(qos: .userInitiated).async {
let task = Shell.user.createTask(
for: "/usr/local/bin/composer global update", requiresPath: true
)
DispatchQueue.main.async {
window?.addToConsole("/usr/local/bin/composer global update\n")
}
Shell.captureOutput(
task,
didReceiveStdOutData: { string in
DispatchQueue.main.async {
window?.addToConsole(string)
}
Log.perf("\(string.trimmingCharacters(in: .newlines))")
},
didReceiveStdErrData: { string in
DispatchQueue.main.async {
window?.addToConsole(string)
}
Log.perf("\(string.trimmingCharacters(in: .newlines))")
}
)
task.launch()
task.waitUntilExit()
Shell.haltCapturingOutput(task)
DispatchQueue.main.async {
if task.terminationStatus <= 0 {
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
window?.close()
if (notify) {
LocalNotification.send(
title: "alert.composer_success.title".localized,
subtitle: "alert.composer_success.info".localized
)
}
window = nil
noLongerBusy()
completion(true)
}
} else {
window?.setType(info: false)
window?.progressView?.labelTitle.stringValue = "alert.composer_failure.title".localized
window?.progressView?.labelDescription.stringValue = "alert.composer_failure.info".localized
window = nil
noLongerBusy()
completion(false)
}
}
}
}
}

View File

@ -0,0 +1,84 @@
//
// MainMenu+PhpSwitcherDelegate.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 08/02/2022.
// Copyright © 2022 Nico Verbruggen. All rights reserved.
//
import Foundation
extension MainMenu {
// MARK: - PhpSwitcherDelegate
func switcherDidStartSwitching(to version: String) {}
func switcherDidCompleteSwitch(to version: String) {
// Update the PHP version
PhpEnv.shared.currentInstall = ActivePhpInstallation()
// Ensure the config watcher gets reloaded
App.shared.handlePhpConfigWatcher()
// Mark as no longer busy
PhpEnv.shared.isBusy = false
// Reload the site list
self.reloadSiteListData()
// Perform UI updates on main thread
DispatchQueue.main.async { [self] in
updatePhpVersionInStatusBar()
rebuild()
if !PhpEnv.shared.validate(version) {
self.suggestFixMyValet(failed: version)
return
}
// Run composer updates
if Preferences.isEnabled(.autoComposerGlobalUpdateAfterSwitch) {
self.updateGlobalDependencies(notify: false, completion: { _ in
self.notifyAboutVersionChange(to: version)
})
} else {
self.notifyAboutVersionChange(to: version)
}
// Update stats
Stats.incrementSuccessfulSwitchCount()
Stats.evaluateSponsorMessageShouldBeDisplayed()
}
}
private func suggestFixMyValet(failed version: String) {
let outcome = Alert.present(
messageText: "alert.php_switch_failed.title".localized(version),
informativeText: "alert.php_switch_failed.info".localized(version),
buttonTitle: "alert.php_switch_failed.confirm".localized,
secondButtonTitle: "alert.php_switch_failed.cancel".localized, style: .informational)
if outcome {
MainMenu.shared.fixMyValet()
}
}
private func reloadSiteListData() {
if let window = App.shared.siteListWindowController {
DispatchQueue.main.async {
window.contentVC.reloadSites()
}
} else {
Valet.shared.reloadSites()
}
}
private func notifyAboutVersionChange(to version: String) {
LocalNotification.send(
title: String(format: "notification.version_changed_title".localized, version),
subtitle: String(format: "notification.version_changed_desc".localized, version)
)
PhpEnv.phpInstall.notifyAboutBrokenPhpFpm()
}
}

View File

@ -7,7 +7,7 @@
import Cocoa
class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate {
class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate, PhpSwitcherDelegate {
static let shared = MainMenu()
@ -23,52 +23,67 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate {
// MARK: - UI related
/**
Update the menu's contents, based on what's going on.
This will rebuild the entire menu, so this can take a few moments.
Rebuilds the menu (either asynchronously or synchronously).
Defaults to rebuilding the menu asynchronously.
*/
func rebuild() {
func rebuild(async: Bool = true) {
if !async {
self.rebuildMenu()
return
}
// Update the menu item on the main thread
DispatchQueue.main.async { [self] in
// Create a new menu
let menu = StatusMenu()
// Add the PHP versions (or error messages)
menu.addPhpVersionMenuItems()
menu.addItem(NSMenuItem.separator())
// Add the possible actions
menu.addPhpActionMenuItems()
menu.addItem(NSMenuItem.separator())
// Add Valet interactions
menu.addValetMenuItems()
menu.addItem(NSMenuItem.separator())
// Add services
menu.addPhpConfigurationMenuItems()
menu.addItem(NSMenuItem.separator())
// Add about & quit menu items
menu.addItem(NSMenuItem(title: "mi_preferences".localized, action: #selector(openPrefs), keyEquivalent: ","))
menu.addItem(NSMenuItem(title: "mi_about".localized, action: #selector(openAbout), keyEquivalent: ""))
menu.addItem(NSMenuItem(title: "mi_quit".localized, action: #selector(terminateApp), keyEquivalent: "q"))
// Make sure every item can be interacted with
menu.items.forEach({ (item) in
item.target = self
})
statusItem.menu = menu
statusItem.menu?.delegate = self
self.rebuildMenu()
}
}
/**
Update the menu's contents, based on what's going on.
This will rebuild the entire menu, so this can take a few moments.
Use `rebuild(async:)` to ensure the rebuilding happens in the background.
*/
private func rebuildMenu() {
// Create a new menu
let menu = StatusMenu()
// Add the PHP versions (or error messages)
menu.addPhpVersionMenuItems()
menu.addItem(NSMenuItem.separator())
// Add the possible actions
menu.addPhpActionMenuItems()
menu.addItem(NSMenuItem.separator())
// Add Valet interactions
menu.addValetMenuItems()
menu.addItem(NSMenuItem.separator())
// Add services
menu.addPhpConfigurationMenuItems()
menu.addItem(NSMenuItem.separator())
// Add about & quit menu items
menu.addItem(NSMenuItem(title: "mi_preferences".localized, action: #selector(openPrefs), keyEquivalent: ","))
menu.addItem(NSMenuItem(title: "mi_about".localized, action: #selector(openAbout), keyEquivalent: ""))
menu.addItem(NSMenuItem(title: "mi_quit".localized, action: #selector(terminateApp), keyEquivalent: "q"))
// Make sure every item can be interacted with
menu.items.forEach({ (item) in
item.target = self
})
statusItem.menu = menu
statusItem.menu?.delegate = self
}
/**
Sets the status bar image based on a version string.
*/
func setStatusBarImage(version: String) {
setStatusBar(
image: Preferences.isEnabled(.shouldDisplayPhpHintInIcon)
image: (Preferences.preferences[.iconTypeToDisplay] as! String != MenuBarIcon.noIcon.rawValue)
? MenuBarImageGenerator.textToImageWithIcon(text: version)
: MenuBarImageGenerator.textToImage(text: version)
)
@ -85,35 +100,9 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate {
}
}
// MARK: - Nicer callbacks
/**
Executes a specific callback and fires the completion callback,
while updating the UI as required. As long as the completion callback
does not fire, the app is presumed to be busy and the UI reflects this.
- Parameter execute: Callback of the work that needs to happen.
- Parameter completion: Callback that is fired when the work is done.
*/
private func waitAndExecute(_ execute: @escaping () -> Void, completion: @escaping () -> Void = {})
{
PhpEnv.shared.isBusy = true
setBusyImage()
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
execute()
PhpEnv.shared.isBusy = false
DispatchQueue.main.async { [self] in
PhpEnv.shared.currentInstall = ActivePhpInstallation()
updatePhpVersionInStatusBar()
NotificationCenter.default.post(name: Events.ServicesUpdated, object: nil)
completion()
}
}
}
// MARK: - User Interface
/** Reloads which PHP versions is currently active. */
@objc func refreshActiveInstallation() {
if !PhpEnv.shared.isBusy {
PhpEnv.shared.currentInstall = ActivePhpInstallation()
@ -123,12 +112,38 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate {
}
}
/** Updates the icon (refresh icon) and rebuilds the menu. */
@objc func updatePhpVersionInStatusBar() {
refreshIcon()
rebuild()
}
func refreshIcon() {
/**
Reloads the menu in the foreground.
This mimics the exact behaviours of `asyncExecution` as set in the method below.
*/
@objc func reloadPhpMonitorMenuInForeground() {
refreshActiveInstallation()
refreshIcon()
rebuild(async: false)
NotificationCenter.default.post(name: Events.ServicesUpdated, object: nil)
}
/** Reloads the menu in the background, using `asyncExecution`. */
@objc func reloadPhpMonitorMenuInBackground() {
asyncExecution({
// This automatically reloads the menu
Log.info("Reloading information about the PHP installation (in the background)...")
}, behaviours: [
.setsBusyUI,
.reloadsPhpInstallation,
.broadcastServicesUpdate,
.updatesMenuBarContents
])
}
/** Refreshes the icon with the PHP version. */
@objc func refreshIcon() {
DispatchQueue.main.async { [self] in
if (App.busy) {
setStatusBar(image: NSImage(named: NSImage.Name("StatusBarIcon"))!)
@ -145,20 +160,7 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate {
}
}
@objc func reloadPhpMonitorMenuInBackground() {
waitAndExecute {
// This automatically reloads the menu
Log.info("Reloading information about the PHP installation (in the background)...")
}
}
@objc func reloadPhpMonitorMenu() {
waitAndExecute {
// This automatically reloads the menu
Log.info("Reloading information about the PHP installation...")
}
}
/** Updates the icon to be displayed as busy. */
@objc func setBusyImage() {
DispatchQueue.main.async { [self] in
setStatusBar(image: NSImage(named: NSImage.Name("StatusBarIcon"))!)
@ -167,18 +169,42 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate {
// MARK: - Actions
@objc func fixHomebrewPermissions() {
if !Alert.present(
messageText: "alert.fix_homebrew_permissions.title".localized,
informativeText: "alert.fix_homebrew_permissions.info".localized,
buttonTitle: "alert.fix_homebrew_permissions.ok".localized,
secondButtonTitle: "alert.fix_homebrew_permissions.cancel".localized,
style: .warning
) {
return
}
asyncExecution {
try Actions.fixHomebrewPermissions()
} success: {
Alert.notify(
message: "alert.fix_homebrew_permissions_done.title".localized,
info: "alert.fix_homebrew_permissions_done.info".localized,
style: .warning
)
} failure: { error in
Alert.notify(about: error as! HomebrewPermissionError)
}
}
@objc func restartPhpFpm() {
waitAndExecute {
asyncExecution {
Actions.restartPhpFpm()
}
}
@objc func restartAllServices() {
waitAndExecute {
asyncExecution {
Actions.restartDnsMasq()
Actions.restartPhpFpm()
Actions.restartNginx()
} completion: {
} success: {
DispatchQueue.main.async {
LocalNotification.send(
title: "notification.services_restarted".localized,
@ -189,9 +215,9 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate {
}
@objc func stopAllServices() {
waitAndExecute {
asyncExecution {
Actions.stopAllServices()
} completion: {
} success: {
DispatchQueue.main.async {
LocalNotification.send(
title: "notification.services_stopped".localized,
@ -202,19 +228,19 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate {
}
@objc func restartNginx() {
waitAndExecute {
asyncExecution {
Actions.restartNginx()
}
}
@objc func restartDnsMasq() {
waitAndExecute {
asyncExecution {
Actions.restartDnsMasq()
}
}
@objc func toggleExtension(sender: ExtensionMenuItem) {
waitAndExecute {
asyncExecution {
sender.phpExtension?.toggle()
if Preferences.isEnabled(.autoServiceRestartAfterExtensionToggle) {
@ -226,34 +252,32 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate {
@objc func openPhpInfo() {
var url: URL? = nil
waitAndExecute {
asyncWithBusyUI {
url = Actions.createTempPhpInfoFile()
} completion: {
// When this has been completed, open the URL to the file in the browser
NSWorkspace.shared.open(url!)
if url != nil { NSWorkspace.shared.open(url!) }
}
}
@objc func fixMyValet() {
// Tell the user the switch is about to occur
if Alert.present(
if !Alert.present(
messageText: "alert.fix_my_valet.title".localized,
informativeText: "alert.fix_my_valet.info".localized(PhpEnv.brewPhpVersion),
buttonTitle: "alert.fix_my_valet.ok".localized,
secondButtonTitle: "alert.fix_my_valet.cancel".localized,
style: .warning
) {
// Start the fix
waitAndExecute {
Actions.fixMyValet()
} completion: {
Alert.notify(
message: "alert.fix_my_valet_done.title".localized,
info: "alert.fix_my_valet_done.info".localized
)
}
} else {
Log.info("The user has chosen to abort Fix My Valet")
return
}
asyncExecution {
Actions.fixMyValet()
} success: {
Alert.notify(
message: "alert.fix_my_valet_done.title".localized,
info: "alert.fix_my_valet_done.info".localized
)
}
}
@ -284,70 +308,26 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate {
self.switchToPhpVersion(sender.version)
}
// TODO (5.1): Investigate if `waitAndExecute` cannot be used here
@objc func switchToPhpVersion(_ version: String) {
setBusyImage()
PhpEnv.shared.isBusy = true
PhpEnv.shared.delegate = self
PhpEnv.shared.delegate?.switcherDidStartSwitching(to: version)
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
// Update the PHP version in the status bar
updatePhpVersionInStatusBar()
// Update the menu
rebuild()
let sendLocalNotification = {
LocalNotification.send(
title: String(format: "notification.version_changed_title".localized, version),
subtitle: String(format: "notification.version_changed_desc".localized, version)
)
PhpEnv.phpInstall.notifyAboutBrokenPhpFpm()
}
let completion = {
// Fire off the delegate method
PhpEnv.shared.delegate?.switcherDidCompleteSwitch()
// Mark as no longer busy
PhpEnv.shared.isBusy = false
// Perform UI updates on main thread
DispatchQueue.main.async { [self] in
updatePhpVersionInStatusBar()
rebuild()
if !PhpEnv.shared.validate(version) {
let outcome = Alert.present(
messageText: "alert.php_switch_failed.title".localized(version),
informativeText: "alert.php_switch_failed.info".localized(version),
buttonTitle: "alert.php_switch_failed.confirm".localized,
secondButtonTitle: "alert.php_switch_failed.cancel".localized, style: .informational)
if outcome {
MainMenu.shared.fixMyValet()
}
return
}
// Run composer updates
if Preferences.isEnabled(.autoComposerGlobalUpdateAfterSwitch) {
self.updateGlobalDependencies(notify: false, completion: { _ in sendLocalNotification() })
} else {
sendLocalNotification()
}
// Update stats
Stats.incrementSuccessfulSwitchCount()
Stats.evaluateSponsorMessageShouldBeDisplayed()
}
}
PhpEnv.switcher.performSwitch(
to: version,
completion: completion
completion: {
PhpEnv.shared.delegate?.switcherDidCompleteSwitch(to: version)
}
)
}
}
// MARK: - Menu Item Functionality
@objc func openAbout() {
NSApplication.shared.activate(ignoringOtherApps: true)
NSApplication.shared.orderFrontStandardAboutPanel()
@ -380,91 +360,4 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate {
// When the menu is closed, allow the shortcut to work again
App.shared.shortcutHotkey?.isPaused = false
}
// MARK: - Private Methods
/**
Updates the global dependencies and runs the completion callback when done.
*/
private func updateGlobalDependencies(notify: Bool, completion: @escaping (Bool) -> Void) {
if !Shell.fileExists("/usr/local/bin/composer") {
Alert.notify(
message: "alert.composer_missing.title".localized,
info: "alert.composer_missing.info".localized
)
return
}
PhpEnv.shared.isBusy = true
setBusyImage()
self.rebuild()
let noLongerBusy = {
PhpEnv.shared.isBusy = false
DispatchQueue.main.async { [self] in
self.updatePhpVersionInStatusBar()
self.rebuild()
}
}
var window: ProgressWindowController? = ProgressWindowController.display(
title: "alert.composer_progress.title".localized,
description: "alert.composer_progress.info".localized
)
window?.setType(info: true)
DispatchQueue.global(qos: .userInitiated).async {
let task = Shell.user.createTask(
for: "/usr/local/bin/composer global update", requiresPath: true
)
DispatchQueue.main.async {
window?.addToConsole("/usr/local/bin/composer global update\n")
}
Shell.captureOutput(
task,
didReceiveStdOutData: { string in
DispatchQueue.main.async {
window?.addToConsole(string)
}
Log.perf("\(string.trimmingCharacters(in: .newlines))")
},
didReceiveStdErrData: { string in
DispatchQueue.main.async {
window?.addToConsole(string)
}
Log.perf("\(string.trimmingCharacters(in: .newlines))")
}
)
task.launch()
task.waitUntilExit()
Shell.haltCapturingOutput(task)
DispatchQueue.main.async {
if task.terminationStatus <= 0 {
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
window?.close()
if (notify) {
LocalNotification.send(
title: "alert.composer_success.title".localized,
subtitle: "alert.composer_success.info".localized
)
}
window = nil
noLongerBusy()
completion(true)
}
} else {
window?.setType(info: false)
window?.progressView?.labelTitle.stringValue = "alert.composer_failure.title".localized
window?.progressView?.labelDescription.stringValue = "alert.composer_failure.info".localized
window = nil
noLongerBusy()
completion(false)
}
}
}
}
}

View File

@ -9,18 +9,18 @@
<customObject id="-2" userLabel="File's Owner"/>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<customView id="c22-O7-iKe" customClass="ServicesView" customModule="PHP_Monitor" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="340" height="46"/>
<customView wantsLayer="YES" id="c22-O7-iKe" customClass="ServicesView" customModule="PHP_Monitor" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="330" height="46"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<stackView distribution="fillEqually" orientation="horizontal" alignment="top" spacing="20" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="TnH-dX-qaQ">
<rect key="frame" x="30" y="3" width="280" height="40"/>
<rect key="frame" x="30" y="3" width="270" height="40"/>
<subviews>
<stackView distribution="fill" orientation="vertical" alignment="centerX" spacing="2" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="doH-ww-BDw">
<rect key="frame" x="0.0" y="4" width="80" height="32"/>
<rect key="frame" x="0.0" y="4" width="77" height="32"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="At1-ch-qv2">
<rect key="frame" x="25" y="18" width="31" height="14"/>
<rect key="frame" x="23" y="18" width="31" height="14"/>
<textFieldCell key="cell" lineBreakMode="clipping" alignment="center" title="PHP" id="LKe-C4-jxo">
<font key="font" metaFont="systemMedium" size="11"/>
<color key="textColor" name="secondaryLabelColor" catalog="System" colorSpace="catalog"/>
@ -28,7 +28,7 @@
</textFieldCell>
</textField>
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="tko-cP-XSz">
<rect key="frame" x="28" y="0.0" width="24" height="16"/>
<rect key="frame" x="26" y="0.0" width="24" height="16"/>
<constraints>
<constraint firstAttribute="height" constant="16" id="Fxu-6h-A2h"/>
<constraint firstAttribute="width" constant="24" id="hOc-Ur-dmA"/>
@ -47,10 +47,10 @@
</customSpacing>
</stackView>
<stackView distribution="fillEqually" orientation="vertical" alignment="centerX" spacing="2" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="g4d-4N-NkC">
<rect key="frame" x="100" y="4" width="80" height="32"/>
<rect key="frame" x="97" y="4" width="76" height="32"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="7um-XA-djV">
<rect key="frame" x="20" y="18" width="40" height="14"/>
<rect key="frame" x="18" y="18" width="40" height="14"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="NGINX" id="Qfq-Bl-yuh">
<font key="font" metaFont="systemMedium" size="11"/>
<color key="textColor" name="secondaryLabelColor" catalog="System" colorSpace="catalog"/>
@ -58,7 +58,7 @@
</textFieldCell>
</textField>
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="ZqW-6d-vpe">
<rect key="frame" x="32" y="0.0" width="16" height="16"/>
<rect key="frame" x="30" y="0.0" width="16" height="16"/>
<constraints>
<constraint firstAttribute="height" constant="16" id="EPG-jm-7Xs"/>
<constraint firstAttribute="width" constant="16" id="iif-kT-phn"/>
@ -77,10 +77,10 @@
</customSpacing>
</stackView>
<stackView distribution="fill" orientation="vertical" alignment="centerX" spacing="2" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="nWj-33-m8Q">
<rect key="frame" x="200" y="4" width="80" height="32"/>
<rect key="frame" x="193" y="4" width="77" height="32"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Oef-6n-9QI">
<rect key="frame" x="9" y="18" width="62" height="14"/>
<rect key="frame" x="8" y="18" width="62" height="14"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="DNSMASQ" id="lGh-MT-TgI">
<font key="font" metaFont="systemMedium" size="11"/>
<color key="textColor" name="secondaryLabelColor" catalog="System" colorSpace="catalog"/>
@ -88,7 +88,7 @@
</textFieldCell>
</textField>
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="DcG-x3-lvy">
<rect key="frame" x="32" y="0.0" width="16" height="16"/>
<rect key="frame" x="31" y="0.0" width="16" height="16"/>
<constraints>
<constraint firstAttribute="width" constant="16" id="AKl-Gq-RtM"/>
<constraint firstAttribute="height" constant="16" id="q2g-Ua-eIJ"/>
@ -145,6 +145,6 @@
</customView>
</objects>
<resources>
<image name="ServiceLoading" width="512" height="512"/>
<image name="ServiceLoading" width="17" height="16"/>
</resources>
</document>

View File

@ -10,11 +10,11 @@
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<customView id="c22-O7-iKe" customClass="StatsView" customModule="PHP_Monitor" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="340" height="55"/>
<rect key="frame" x="0.0" y="0.0" width="330" height="55"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<stackView distribution="fillEqually" orientation="horizontal" alignment="top" spacing="20" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="TnH-dX-qaQ">
<rect key="frame" x="30" y="6" width="280" height="43"/>
<rect key="frame" x="30" y="6" width="270" height="43"/>
<subviews>
<stackView distribution="fill" orientation="vertical" alignment="centerX" spacing="2" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="doH-ww-BDw">
<rect key="frame" x="0.0" y="4" width="87" height="35"/>
@ -46,10 +46,10 @@
</customSpacing>
</stackView>
<stackView distribution="fillEqually" orientation="vertical" alignment="centerX" spacing="2" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="g4d-4N-NkC">
<rect key="frame" x="107" y="4" width="77" height="35"/>
<rect key="frame" x="107" y="4" width="68" height="35"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="7um-XA-djV">
<rect key="frame" x="7" y="21" width="63" height="14"/>
<rect key="frame" x="3" y="21" width="63" height="14"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="MAX POST" id="Qfq-Bl-yuh">
<font key="font" metaFont="systemMedium" size="11"/>
<color key="textColor" name="secondaryLabelColor" catalog="System" colorSpace="catalog"/>
@ -57,7 +57,7 @@
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Vyu-AO-8SH">
<rect key="frame" x="11" y="0.0" width="55" height="19"/>
<rect key="frame" x="7" y="0.0" width="55" height="19"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="1024M" id="uH4-Zy-43x">
<font key="font" metaFont="systemMedium" size="16"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@ -75,7 +75,7 @@
</customSpacing>
</stackView>
<stackView distribution="fill" orientation="vertical" alignment="centerX" spacing="2" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="nWj-33-m8Q">
<rect key="frame" x="204" y="4" width="76" height="35"/>
<rect key="frame" x="195" y="4" width="75" height="35"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Oef-6n-9QI">
<rect key="frame" x="-2" y="21" width="79" height="14"/>

View File

@ -8,6 +8,7 @@
import Cocoa
class StatusMenu : NSMenu {
func addPhpVersionMenuItems() {
if PhpEnv.phpInstall.version.error {
for message in ["mi_php_broken_1", "mi_php_broken_2", "mi_php_broken_3", "mi_php_broken_4"] {
@ -37,47 +38,6 @@ class StatusMenu : NSMenu {
self.addItem(NSMenuItem.separator())
}
func addOtherMenuItems() {
let services = NSMenuItem(title: "mi_other".localized, action: nil, keyEquivalent: "")
let servicesMenu = NSMenu()
servicesMenu.addItem(NSMenuItem(title: "mi_help".localized, action: nil, keyEquivalent: ""))
if !PhpEnv.shared.availablePhpVersions.contains(PhpEnv.brewPhpVersion) {
servicesMenu.addItem(NSMenuItem(
title: "mi_fix_my_valet_unavailable".localized(PhpEnv.brewPhpVersion),
action: nil, keyEquivalent: "f"
))
} else {
servicesMenu.addItem(NSMenuItem(
title: "mi_fix_my_valet".localized(PhpEnv.brewPhpVersion),
action: #selector(MainMenu.fixMyValet), keyEquivalent: "f"))
}
servicesMenu.addItem(NSMenuItem(title: "mi_services".localized, action: nil, keyEquivalent: ""))
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_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])
servicesMenu.addItem(NSMenuItem(title: "mi_restart_all_services".localized, action: #selector(MainMenu.restartAllServices), keyEquivalent: "s"))
servicesMenu.addItem(NSMenuItem(title: "mi_manual_actions".localized, action: nil, keyEquivalent: ""))
servicesMenu.addItem(NSMenuItem(title: "mi_php_refresh".localized, action: #selector(MainMenu.reloadPhpMonitorMenu), keyEquivalent: "r"))
for item in servicesMenu.items {
item.target = MainMenu.shared
}
self.setSubmenu(servicesMenu, for: services)
self.addItem(services)
}
func addValetMenuItems() {
self.addItem(HeaderView.asMenuItem(text: "mi_valet".localized))
self.addItem(NSMenuItem(title: "mi_valet_config".localized, action: #selector(MainMenu.openValetConfigFolder), keyEquivalent: "v"))
@ -131,9 +91,59 @@ class StatusMenu : NSMenu {
// Other
self.addItem(NSMenuItem.separator())
self.addOtherMenuItems()
self.addFirstAidAndServicesMenuItems()
}
func addFirstAidAndServicesMenuItems() {
let services = NSMenuItem(title: "mi_other".localized, action: nil, keyEquivalent: "")
let servicesMenu = NSMenu()
servicesMenu.addItem(HeaderView.asMenuItem(text: "mi_first_aid".localized))
if !PhpEnv.shared.availablePhpVersions.contains(PhpEnv.brewPhpVersion) {
servicesMenu.addItem(NSMenuItem(
title: "mi_fix_my_valet_unavailable".localized(PhpEnv.brewPhpVersion),
action: nil, keyEquivalent: "f")
)
} else {
servicesMenu.addItem(NSMenuItem(
title: "mi_fix_my_valet".localized(PhpEnv.brewPhpVersion),
action: #selector(MainMenu.fixMyValet), keyEquivalent: "")
)
}
/* (disabled until v5.1 and further tweaking)
servicesMenu.addItem(NSMenuItem(
title: "mi_fix_brew_permissions".localized(),
action: #selector(MainMenu.fixHomebrewPermissions), keyEquivalent: "")
)
*/
servicesMenu.addItem(NSMenuItem.separator())
servicesMenu.addItem(HeaderView.asMenuItem(text: "mi_services".localized))
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_nginx".localized, action: #selector(MainMenu.restartNginx), keyEquivalent: "n"))
servicesMenu.addItem(NSMenuItem(title: "mi_restart_all_services".localized, action: #selector(MainMenu.restartAllServices), keyEquivalent: "s"))
servicesMenu.addItem(
NSMenuItem(title: "mi_stop_all_services".localized, action: #selector(MainMenu.stopAllServices), keyEquivalent: "s"),
withKeyModifier: [.command, .shift])
servicesMenu.addItem(NSMenuItem.separator())
servicesMenu.addItem(HeaderView.asMenuItem(text: "mi_manual_actions".localized))
servicesMenu.addItem(NSMenuItem(title: "mi_php_refresh".localized, action: #selector(MainMenu.reloadPhpMonitorMenuInForeground), keyEquivalent: "r"))
for item in servicesMenu.items {
item.target = MainMenu.shared
}
self.setSubmenu(servicesMenu, for: services)
self.addItem(services)
}
// PRIVATE HELPERS
private func addSwitchToPhpMenuItems() {
var shortcutKey = 1
for index in (0..<PhpEnv.shared.availablePhpVersions.count).reversed() {

View File

@ -0,0 +1,16 @@
//
// MenuBarIcons.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 06/02/2022.
// Copyright © 2022 Nico Verbruggen. All rights reserved.
//
import Foundation
import Cocoa
enum MenuBarIcon: String, CaseIterable {
case iconPhp = "php"
case iconElephant = "elephant"
case noIcon = "none"
}

View File

@ -14,7 +14,7 @@ import Foundation
enum PreferenceName: String {
case wasLaunchedBefore = "launched_before"
case shouldDisplayDynamicIcon = "use_dynamic_icon"
case shouldDisplayPhpHintInIcon = "add_php_to_icon"
case iconTypeToDisplay = "icon_type_to_display"
case fullPhpVersionDynamicIcon = "full_php_in_menu_bar"
case autoServiceRestartAfterExtensionToggle = "auto_restart_after_extension_toggle"
case autoComposerGlobalUpdateAfterSwitch = "auto_composer_global_update_after_switch"
@ -22,6 +22,13 @@ enum PreferenceName: String {
case globalHotkey = "global_hotkey"
}
/**
These are retired preferences that, if present, should be migrated.
*/
enum RetiredPreferenceName: String {
case shouldDisplayPhpHintInIcon = "add_php_to_icon"
}
/**
These are internal stats. They NEVER get shared.
*/
@ -64,7 +71,7 @@ class Preferences {
UserDefaults.standard.register(defaults: [
/// Preferences
PreferenceName.shouldDisplayDynamicIcon.rawValue: true,
PreferenceName.shouldDisplayPhpHintInIcon.rawValue: true,
PreferenceName.iconTypeToDisplay.rawValue: MenuBarIcon.iconPhp.rawValue,
PreferenceName.fullPhpVersionDynamicIcon.rawValue: false,
PreferenceName.autoServiceRestartAfterExtensionToggle.rawValue: true,
PreferenceName.autoComposerGlobalUpdateAfterSwitch.rawValue: false,
@ -76,6 +83,7 @@ class Preferences {
])
if UserDefaults.standard.bool(forKey: PreferenceName.wasLaunchedBefore.rawValue) {
handleMigration()
return
}
@ -84,6 +92,23 @@ class Preferences {
UserDefaults.standard.synchronize()
}
/**
Sometimes preferences will change, and a migration is required to take the user's previous preference
and migrate it over to the new type. For example, the choice to disable the icon next to the version
number was once a boolean (do you want the icon? yes / no) but has now become a multi-faceted option.
*/
static func handleMigration() {
// If the user chose the "no icon" option, migrate it over
if (
UserDefaults.standard.value(forKey: RetiredPreferenceName.shouldDisplayPhpHintInIcon.rawValue) != nil &&
UserDefaults.standard.bool(forKey: RetiredPreferenceName.shouldDisplayPhpHintInIcon.rawValue) == false
) {
Log.info("The preference where the user chose no icon has been migrated over.")
UserDefaults.standard.set(MenuBarIcon.noIcon.rawValue, forKey: PreferenceName.iconTypeToDisplay.rawValue)
UserDefaults.standard.removeObject(forKey: RetiredPreferenceName.shouldDisplayPhpHintInIcon.rawValue)
}
}
// MARK: - API
static var preferences: [PreferenceName: Any?] {
@ -112,7 +137,6 @@ class Preferences {
return [
// Part 1: Always Booleans
.shouldDisplayDynamicIcon: UserDefaults.standard.bool(forKey: PreferenceName.shouldDisplayDynamicIcon.rawValue) as Any,
.shouldDisplayPhpHintInIcon: UserDefaults.standard.bool(forKey: PreferenceName.shouldDisplayPhpHintInIcon.rawValue) as Any,
.fullPhpVersionDynamicIcon: UserDefaults.standard.bool(forKey: PreferenceName.fullPhpVersionDynamicIcon.rawValue) as Any,
.autoServiceRestartAfterExtensionToggle: UserDefaults.standard.bool(forKey: PreferenceName.autoServiceRestartAfterExtensionToggle.rawValue) as Any,
.autoComposerGlobalUpdateAfterSwitch: UserDefaults.standard.bool(forKey: PreferenceName.autoComposerGlobalUpdateAfterSwitch.rawValue) as Any,
@ -120,6 +144,7 @@ class Preferences {
// Part 2: Always Strings
.globalHotkey: UserDefaults.standard.string(forKey: PreferenceName.globalHotkey.rawValue) as Any,
.iconTypeToDisplay: UserDefaults.standard.string(forKey: PreferenceName.iconTypeToDisplay.rawValue) as Any,
]
}

View File

@ -57,11 +57,12 @@ class PrefsVC: NSViewController {
MainMenu.shared.refreshIcon()
}
),
CheckboxPreferenceView.make(
SelectPreferenceView.make(
sectionText: "",
descriptionText: "prefs.icon_hint_desc".localized,
checkboxText: "prefs.icon_hint_title".localized,
preference: .shouldDisplayPhpHintInIcon,
descriptionText: "prefs.icon_options_desc".localized,
options: MenuBarIcon.allCases.map({ return $0.rawValue }),
localizationPrefix: "prefs.icon_options",
preference: .iconTypeToDisplay,
action: {
MainMenu.shared.refreshIcon()
}

View File

@ -61,7 +61,7 @@
<outlet property="labelDescription" destination="Bcg-X1-qca" id="T23-ag-AUf"/>
<outlet property="labelSection" destination="B8f-nb-Y0A" id="i61-ls-yM0"/>
</connections>
<point key="canvasLocation" x="149" y="-114.5"/>
<point key="canvasLocation" x="149" y="-115"/>
</customView>
</objects>
</document>

View File

@ -0,0 +1,82 @@
//
// SelectPreferenceView.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 06/02/2022.
// Copyright © 2022 Nico Verbruggen. All rights reserved.
//
import Foundation
import Cocoa
class SelectPreferenceView: NSView, XibLoadable {
@IBOutlet weak var labelSection: NSTextField!
@IBOutlet weak var labelDescription: NSTextField!
@IBOutlet weak var popupButton: NSPopUpButton!
var localizationPrefix: String = ""
var imagePrefix: String? = nil
var options: [String] = [] {
didSet {
self.popupButton.removeAllItems()
self.options.forEach { value in
self.popupButton.addItem(
withTitle: "\(localizationPrefix).\(value)".localized
)
}
if imagePrefix == nil {
return
}
self.popupButton.itemArray.enumerated().forEach { item in
item.element.image = NSImage(named: "\(imagePrefix!)_\(self.options[item.offset])")
}
}
}
var action: (() -> Void)!
var preference: PreferenceName! {
didSet {
let value = Preferences.preferences[preference] as! String
self.options.enumerated().forEach { option in
if option.element == value {
self.popupButton.selectItem(at: option.offset)
}
}
}
}
static func make(
sectionText: String,
descriptionText: String,
options: [String],
localizationPrefix: String,
imagePrefix: String? = nil,
preference: PreferenceName,
action: @escaping () -> Void) -> NSView
{
let view = Self.createFromXib()!
view.labelSection.stringValue = sectionText
view.labelDescription.stringValue = descriptionText
view.localizationPrefix = localizationPrefix
view.imagePrefix = imagePrefix
view.options = options
view.preference = preference
view.action = action
return view
}
@IBAction func valueChanged(_ sender: Any) {
let index = self.popupButton.indexOfSelectedItem
Preferences.update(.iconTypeToDisplay, value: self.options[index])
self.action()
}
}

View File

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="19529" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="19529"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner"/>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<customView id="c22-O7-iKe" customClass="SelectPreferenceView" customModule="PHP_Monitor" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="596" height="50"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Bcg-X1-qca">
<rect key="frame" x="168" y="5" width="410" height="14"/>
<textFieldCell key="cell" title="DESCRIPTION" id="9fH-up-Sob">
<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="B8f-nb-Y0A">
<rect key="frame" x="-2" y="29" width="154" height="16"/>
<constraints>
<constraint firstAttribute="width" constant="150" id="euj-t0-xv4"/>
</constraints>
<textFieldCell key="cell" lineBreakMode="clipping" alignment="right" title="SECTION" id="46w-Sv-y21">
<font key="font" metaFont="systemMedium" size="13"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="YaB-Tg-Ir3">
<rect key="frame" x="167" y="23" width="110" height="25"/>
<popUpButtonCell key="cell" type="push" title="Icon Option" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="SaA-mm-HBo" id="Su6-C4-aGo">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<menu key="menu" id="WjV-7P-3WA">
<items>
<menuItem title="Icon Option" state="on" id="SaA-mm-HBo"/>
<menuItem title="Item 2" id="I5Z-Ka-zo6"/>
<menuItem title="Item 3" id="qUM-nV-HdU"/>
</items>
</menu>
</popUpButtonCell>
<connections>
<action selector="valueChanged:" target="c22-O7-iKe" id="M95-aC-xtz"/>
</connections>
</popUpButton>
</subviews>
<constraints>
<constraint firstItem="YaB-Tg-Ir3" firstAttribute="centerY" secondItem="B8f-nb-Y0A" secondAttribute="centerY" id="1t0-bW-LpQ"/>
<constraint firstItem="B8f-nb-Y0A" firstAttribute="top" secondItem="c22-O7-iKe" secondAttribute="top" constant="5" id="2Zu-h3-qb0"/>
<constraint firstItem="Bcg-X1-qca" firstAttribute="leading" secondItem="YaB-Tg-Ir3" secondAttribute="leading" id="5IQ-Nb-Q3V"/>
<constraint firstItem="YaB-Tg-Ir3" firstAttribute="leading" secondItem="B8f-nb-Y0A" secondAttribute="trailing" constant="20" id="6sr-bU-xvG"/>
<constraint firstItem="Bcg-X1-qca" firstAttribute="top" secondItem="YaB-Tg-Ir3" secondAttribute="bottom" constant="8" symbolic="YES" id="Mji-pe-CNl"/>
<constraint firstAttribute="trailing" secondItem="Bcg-X1-qca" secondAttribute="trailing" constant="20" symbolic="YES" id="UPo-Il-l81"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="YaB-Tg-Ir3" secondAttribute="trailing" constant="20" symbolic="YES" id="Zlg-jj-uKY"/>
<constraint firstItem="B8f-nb-Y0A" firstAttribute="leading" secondItem="c22-O7-iKe" secondAttribute="leading" id="Ztd-uk-4aw"/>
<constraint firstAttribute="bottom" secondItem="Bcg-X1-qca" secondAttribute="bottom" constant="5" id="hNE-mU-jcu"/>
</constraints>
<connections>
<outlet property="labelDescription" destination="Bcg-X1-qca" id="T23-ag-AUf"/>
<outlet property="labelSection" destination="B8f-nb-Y0A" id="i61-ls-yM0"/>
<outlet property="popupButton" destination="YaB-Tg-Ir3" id="sAB-BL-qUQ"/>
</connections>
<point key="canvasLocation" x="149" y="-115"/>
</customView>
</objects>
</document>

View File

@ -47,7 +47,6 @@ class SiteListCell: NSTableCellView
imageViewType.contentTintColor = NSColor.tertiaryLabelColor
// Show the green or red lock based on whether the site was secured
// imageViewLock.image = NSImage(named: site.secured ? "Lock" : "LockUnlocked")
imageViewLock.contentTintColor = site.secured ?
NSColor(named: "IconColorGreen") // green
: NSColor(named: "IconColorRed")
@ -103,7 +102,7 @@ class SiteListCell: NSTableCellView
if response.rawValue > NSApplication.ModalResponse.alertFirstButtonReturn.rawValue {
if map.keys.contains(response.rawValue) {
let version = map[response.rawValue]!
print("Pressed button to switch to \(version)")
Log.info("Pressed button to switch to \(version)")
MainMenu.shared.switchToPhpVersion(version)
}
}

View File

@ -39,6 +39,8 @@ class SiteListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource {
return sites[tableView.selectedRow]
}
var timer: Timer? = nil
// MARK: - Display
public static func create(delegate: NSWindowDelegate?) {
@ -90,7 +92,11 @@ class SiteListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource {
Also shows a spinner to indicate that we're busy.
*/
private func setUIBusy() {
progressIndicator.startAnimation(nil)
// If it takes more than 0.5s to set the UI to not busy, show a spinner
timer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false, block: { _ in
self.progressIndicator.startAnimation(true)
})
tableView.alphaValue = 0.3
tableView.isEnabled = false
tableView.selectRowIndexes([], byExtendingSelection: true)
@ -100,6 +106,7 @@ class SiteListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource {
Re-enables the UI so the user can interact with it.
*/
private func setUINotBusy() {
timer?.invalidate()
progressIndicator.stopAnimation(nil)
tableView.alphaValue = 1.0
tableView.isEnabled = true
@ -118,7 +125,9 @@ class SiteListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource {
setUIBusy()
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
execute()
DispatchQueue.main.async { [self] in
// For a smoother animation, expect at least a 0.2 second delay
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { [self] in
completion()
setUINotBusy()
}

View File

@ -30,17 +30,22 @@ extension App {
func handlePhpConfigWatcher(forceReload: Bool = false) {
let url = URL(fileURLWithPath: "\(Paths.etcPath)/php/\(PhpEnv.phpInstall.version.short)")
// Watcher needs to be created
if self.watcher == nil {
startWatcher(url)
}
// Watcher needs to be updated
if self.watcher.url != url || forceReload {
self.watcher.disable()
self.watcher = nil
Log.info("Watcher has stopped watching files. Starting new one...")
startWatcher(url)
// Check whether the watcher exists and schedule on the main thread
// if we don't consistently do this, the app will create duplicate watchers
// due to timing issues, which creates retain cycles.
DispatchQueue.main.async {
// Watcher needs to be created
if self.watcher == nil {
self.startWatcher(url)
}
// Watcher needs to be updated
if self.watcher.url != url || forceReload {
self.watcher.disable()
self.watcher = nil
Log.info("Watcher has stopped watching files. Starting new one...")
self.startWatcher(url)
}
}
}

View File

@ -40,19 +40,24 @@ class PhpConfigWatcher {
self.addWatcher(for: self.url.appendingPathComponent("conf.d/\(file)"), eventMask: .write)
}
Log.info("A watcher exists for the following config paths:")
Log.info(self.watchers.map({ watcher in
Log.perf("A watcher exists for the following config paths:")
Log.perf(self.watchers.map({ watcher in
return watcher.url.relativePath
}))
}
func addWatcher(for url: URL, eventMask: DispatchSource.FileSystemEvent, behaviour: FSWatcherBehaviour = .reloadsMenu) {
if !Filesystem.fileExists(url.path) {
Log.warn("No watcher was created for \(url.path) because the requested file does not exist.")
return
}
let watcher = FSWatcher(for: url, eventMask: eventMask, parent: self, behaviour: behaviour)
self.watchers.append(watcher)
}
func disable() {
Log.info("Turning off existing watchers...")
Log.perf("Turning off all individual existing watchers...")
self.watchers.forEach { (watcher) in
watcher.stopMonitoring()
}

View File

@ -27,6 +27,7 @@
"mi_stop_all_services" = "Stop All Services";
"mi_fix_my_valet" = "Fix My Valet (PHP & Services)";
"mi_fix_my_valet_unavailable" = "Fix My Valet Unavailable";
"mi_fix_brew_permissions" = "Restore Homebrew Permissions";
"mi_php_refresh" = "Refresh Information";
"mi_configuration" = "PHP Configuration";
@ -36,11 +37,11 @@
"mi_upload_max_filesize" = "Max Upload";
"mi_manual_actions" = "Manual Actions";
"mi_services" = "Services";
"mi_help" = "First Aid";
"mi_other" = "First Aid & Services";
"mi_first_aid" = "First Aid";
"mi_composer" = "Composer";
"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_global_composer" = "Locate Global Composer File (.composer)";
"mi_phpinfo" = "Show Current Configuration (phpinfo)";
@ -132,22 +133,26 @@
"prefs.subtitle" = "Preferences";
"prefs.close" = "Close";
"prefs.global_shortcut" = "Global shortcut:";
"prefs.dynamic_icon" = "Icon customization:";
"prefs.info_density" = "Info density:";
"prefs.global_shortcut" = "Global Shortcut:";
"prefs.dynamic_icon" = "Dynamic Icon:";
"prefs.dynamic_icon" = "Icon Type:";
"prefs.info_density" = "Info Density:";
"prefs.services" = "Services:";
"prefs.switcher" = "Switcher:";
"prefs.integrations" = "Integrations:";
"prefs.icon_options.php" = "Display PHP Icon";
"prefs.icon_options.elephant" = "Display Elephant Icon";
"prefs.icon_options.none" = "Do Not Display Icon";
"prefs.icon_options_desc" = "This option decides which icon will be displayed next to the version number of the currently linked PHP version. If the Dynamic Icon option has been disabled, this will have no effect.";
"prefs.auto_restart_services_title" = "Auto-restart PHP-FPM";
"prefs.auto_restart_services_desc" = "When checked, will automatically restart PHP-FPM when you check or uncheck an extension. Slightly slower when enabled, but this applies the extension change immediately for all sites you'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. If checked, it will display the major version number of the currently linked PHP version.";
"prefs.icon_hint_title" = "Display PHP hint next to version number";
"prefs.icon_hint_desc" = "If you uncheck this box, the icon in the menu bar will only show the version number and not the PHP hint. This preference does not do anything if the dynamic icon has been disabled.";
"prefs.display_full_php_version" = "Display full PHP version everywhere";
"prefs.display_full_php_version_desc" = "Display the full version instead of the major version displayed in the menu bar and the dropdown menu. (This may be undesirable on smaller displays, so this is disabled by default.)";
@ -180,7 +185,7 @@
// Composer Update
"alert.composer_missing.title" = "Composer not found!";
"alert.composer_missing.info" = "Make sure you have Composer available in `/usr/local/bin/composer`. If Composer is located somewhere else, please create a symlink, like so (make sure to use the correct path):\n\n`ln -s /path/to/composer /user/local/bin`.";
"alert.composer_missing.info" = "Make sure you have Composer available in `/usr/local/bin/composer`. If Composer is located somewhere else, please create a symlink, like so (make sure to use the correct path):\n\n`ln -s /path/to/composer /usr/local/bin`.";
"alert.composer_progress.title" = "Updating global dependencies...";
"alert.composer_progress.info" = "You can see the progress in the terminal output below.";
@ -213,6 +218,15 @@ problem manually, using your own Terminal app (this just shows you the output)."
"alert.fix_my_valet_done.title" = "Fix My Valet has completed its operations.";
"alert.fix_my_valet_done.info" = "All appropriate services have been stopped and the correct ones restarted, and the latest version of PHP should now be active. You can now try switching to another version of PHP.\n\nIf visiting sites still does not work, you may try running `valet install` again, this can fix a 502 issue (Bad Gateway).\n\nIf Valet is broken and you cannot run `valet install`, you may need to run `composer global update`. Please consult the FAQ on GitHub if you have further issues.";
// Restore Homebrew Permissions
"alert.fix_homebrew_permissions.title" = "About \"Restore Homebrew Permissions\"";
"alert.fix_homebrew_permissions.info" = "This feature was created so you can run `brew upgrade` or `brew cleanup` without permission issues.\n\nThis will require administrative privileges, because PHP Monitor will restore your ownership of the files and folders that are currently owned by the `root` user, due to Valet services running as root.\n\n(You will be notified when this fix has been applied.)";
"alert.fix_homebrew_permissions.ok" = "Restore Permissions";
"alert.fix_homebrew_permissions.cancel" = "Cancel";
"alert.fix_homebrew_permissions_done.title" = "Permissions Fixed!";
"alert.fix_homebrew_permissions_done.info" = "When you are done with Homebrew, you should open PHP Monitor and restart all services if you want Valet to work again. It is also recommended to restart PHP Monitor after running `brew upgrade`.";
// 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.";
@ -278,3 +292,8 @@ You can do this by running `composer global update` in your terminal. After that
"startup.sponsor_encouragement.desc" = "If you have already donated, then YOU are the reason why the app was able to get all these new features. In that case, this is a THANK YOU message to you.\n\nTo be 100% transparent: I plan to keep PHP Monitor open source and free. Your support makes this decision very easy.\n\n(You will only see this prompt once.)";
"startup.sponsor_encouragement.accept" = "Yes, I would like to sponsor";
"startup.sponsor_encouragement.skip" = "Nevermind";
// ERROR MESSAGES (based on AlertableError)
"alert.errors.homebrew_permissions.applescript_returned_nil.title" = "Restore Homebrew Permissions has been cancelled.";
"alert.errors.homebrew_permissions.applescript_returned_nil.description" = "The outcome of the script that is executed to adjust the permissions returned nil, which usually means that you did not grant administrative permissions to PHP Monitor.\n\nIf you clicked on Cancel during the authentication prompt, this is normal. If you did actually authenticate and you are still seeing this message, something probably went wrong.";